유민형 2023-11-22 16:36:39 +09:00
commit eb12c300f5
20 changed files with 1259 additions and 18 deletions

View File

@ -4,7 +4,8 @@ const URL = {
MAIN : "/", //메인페이지
LOGIN : "/login", //로그인
ERROR : "/error", //로그인
JOIN : "/join", //회원가입
ERROR : "/error", //에러
//ABOUT
ABOUT : "/about", //사이트소개

View File

@ -84,4 +84,4 @@ button {cursor: pointer;}
.detailInfoDiv > div > p { display: inline; }
.detailInfoDiv > div > input {margin-right: 5px;}
.detailInfoDiv > div > input {margin-right: 5px;}

View File

@ -12,13 +12,26 @@
.Plogin .login_box input[type=password]:-ms-input-placeholder {color: #aaa; opacity: 1;}
.Plogin .login_box button {display: block; position: absolute; left: 435px; top: 70px; width: 160px; height: 110px; border-radius: 8px; color: #fff; font-size: 20px; font-weight: 500; text-align: center; line-height: 110px; background: #169bd5;}
.Plogin .login_box button span {display: block; position: relative; height: 100%;}
.Plogin .login_box .chk {margin-top: 20px; font-size: 0;}
.Plogin .login_box .chk {margin-top: 20px;}
.Plogin .login_box .chk em {display: inline-block; height: 30px; margin-left: 40px; color: #666; font-size: 16px;}
.Plogin .list {margin-top: 44px; padding: 0 360px;}
.Plogin .list li {position: relative; padding-left: 15px; color: #666; font-size: 16px; line-height: 26px;}
.Plogin .list li::before {content: ""; display: block; position: absolute; left: 0; top: 12px; width: 4px; height: 4px; background: #666;}
.Plogin .list li + li {margin-top: 5px;}
.Pjoin h1 {color: #222; font-size: 48px; font-weight: 500; letter-spacing: -2px; line-height: 48px; text-align: center;}
.Pjoin .join_box {position: relative; width: 690px; margin: 54px auto 0; padding: 70px 95px 120px 95px ; border: 1px solid #dde2e5; border-radius: 25px; box-shadow: 3px 4px 5px #ccc;}
.Pjoin .join_box input[type=text],
.Pjoin .join_box input[type=password] {width: 100%; height: 46px; padding: 0 20px; border: 0; border-radius: 8px; color: #666; font-size: 16px; background: #f5f5f5;}
.Pjoin .join_box .group input + input {margin-top: 18px;}
.Pjoin .join_box input[type=text]:-ms-input-placeholder,
.Pjoin .join_box input[type=password]:-ms-input-placeholder {color: #aaa; opacity: 1;}
.Pjoin .join_box .chk {margin-top: 20px;}
.Pjoin .join_box .chk em {display: inline-block; height: 30px; margin-left: 40px; color: #666; font-size: 16px;}
.Pjoin .join_box button {width: 500px;height: 50px;border-radius: 8px;color: #fff;font-size: 20px;font-weight: 500;text-align: center;line-height: 50px;background: #169bd5;}
.Pjoin .join_box button span {display: block; position: relative; height: 100%;}
.Pjoin .join_box .list li {position: relative; padding-left: 15px; color: #666; font-size: 16px; line-height: 26px;}
.Pjoin .join_box .list li + li {margin-top: 5px;}
/* Board */

View File

@ -1,9 +1,12 @@
import React, { useState, useEffect, useRef } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import {Link, useLocation, useNavigate} from 'react-router-dom';
import * as EgovNet from 'api/egovFetch';
import URL from 'constants/url';
import CODE from 'constants/code';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import { getLocalItem, setLocalItem, setSessionItem } from 'utils/storage';
function EgovLoginContent(props) {
@ -113,11 +116,16 @@ function EgovLoginContent(props) {
<input type="password" name="" title="비밀번호" placeholder="비밀번호"
onChange={e => setUserInfo({ ...userInfo, password: e.target.value })} />
</span>
<div className="chk">
<label className="f_chk" htmlFor="saveid" ref={checkRef}>
<input type="checkbox" name="" id="saveid" onChange={handleSaveIDFlag} checked={saveIDFlag}/> <em>ID저장</em>
</label>
</div>
<Row className="chk justify-content-between">
<Col xs={3}>
<label className="f_chk" htmlFor="saveid" ref={checkRef}>
<input type="checkbox" name="" id="saveid" onChange={handleSaveIDFlag} checked={saveIDFlag}/> <em>ID저장</em>
</label>
</Col>
<Col xs={3}>
<Link to={URL.JOIN}><em>회원가입</em></Link>
</Col>
</Row>
<button type="button" onClick={submitFormHandler}><span>LOGIN</span></button>
</fieldset>
</form>

View File

@ -0,0 +1,20 @@
import React from 'react'
function InfoShareChk() {
return (
<ul className="list">
<li>건설기준 포털시스템(KCSC) 회원님께 원활한 서비스 제공을 위해 최소한의 개인정보를 수집하고 있으며, 목적 별로 수집하는 개인정보를 다음과 같이 정하고 있습니다.</li>
<li className="no_dot">&nbsp;</li>
<li className="no_dot"> 수집하는 개인정보의 항목</li>
<li className="no_dot">&nbsp;&nbsp;&nbsp;수집항목: 아이디(ID), 비밀번호, 이메일(e-mail)</li>
<li className="no_dot"> 개인정보의 수집 이용 목적</li>
<li className="no_dot">&nbsp;&nbsp;&nbsp;이용목적 : 건설기준 포털시스템(KCSC) 회원가입 Q&amp;A 작성</li>
<li className="no_dot"> 개인정보의 보류 이용기간</li>
<li className="no_dot">&nbsp;&nbsp;&nbsp;회원가입정보 : 탈퇴시까지</li>
<li className="no_dot"> 정보주체의 동의 거부 권리 그에 따른 불이익 사항</li>
<li className="no_dot">&nbsp;</li>
<li>귀하는 건설기준 포털시스템(KCSC) 이용을 위한 개인정보의 수집·이용 동의를 거부할 권리가 있습니다. 다만, 개인정보의 수집·이용 동의를 거부할 경우 건설기준 포털시스템(KCSC) 일부 기능에 대한 서비스 이용에 제한을 받으실 있습니다.</li>
</ul>
)
}
export default InfoShareChk;

View File

@ -0,0 +1,116 @@
import React, { useState, useEffect, useRef } from 'react';
import {Link, useLocation, useNavigate} from 'react-router-dom';
import * as EgovNet from 'api/egovFetch';
import URL from 'constants/url';
import CODE from 'constants/code';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import { getLocalItem, setLocalItem, setSessionItem } from 'utils/storage';
import EgovLoginContent from "../EgovLoginContent";
import InfoShareChk from "./InfoShareChk";
function Join(props) {
console.group("JoinContent");
console.log("[Start] JoinContent ------------------------------");
console.log("JoinContent [props] : ", props);
const navigate = useNavigate();
const location = useLocation();
console.log("JoinContent [location] : ", location);
const [userInfo, setUserInfo] = useState({ id: '', email: '', password: '' });
const [infoShareChk, setInfoShareChk] = useState(false);
const submitFormHandler = (e) => {
console.log("JoinContent submitFormHandler()");
const loginUrl = "/auth/join"
const requestOptions = {
method: "POST",
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify(userInfo)
}
EgovNet.requestFetch(loginUrl,
requestOptions,
(resp) => {
let resultVO = resp.resultVO;
let jToken = resp?.jToken || null;
setSessionItem('jToken', jToken);
})
}
console.log("------------------------------JoinContent [End]");
console.groupEnd("JoinContent");
const infoShareBtn = () =>{
setInfoShareChk(true)
}
return (
<div className="container">
<div className="c_wrap">
{/* <!-- Location --> */}
<div className="location">
<ul>
<li><Link to={URL.MAIN} className="home" >Home</Link></li>
<li><Link to={URL.LOGIN}>로그인</Link></li>
<li>회원가입</li>
</ul>
</div>
{/* <!--// Location --> */}
<div className="layout">
<div className="contents" id="contents">
{/* <!-- 본문 --> */}
<div className="Pjoin">
<h1>회원가입</h1>
<div className="join_box">
{infoShareChk?(
<form name="" method="" action="" >
<span className="group">
<input type="text" name="" title="아이디" placeholder="아이디" value={userInfo?.id}
onChange={e => setUserInfo({ ...userInfo, id: e.target.value })} />
<input type="text" name="" title="이메일" placeholder="이메일" value={userInfo?.email}
onChange={e => setUserInfo({ ...userInfo, email: e.target.value })} />
<input type="password" name="" title="비밀번호" placeholder="비밀번호"
onChange={e => setUserInfo({ ...userInfo, password: e.target.value })} />
<input type="password" name="" title="비밀번호 확인" placeholder="비밀번호 확인"
onChange={e => setUserInfo({ ...userInfo, passwordConfirm: e.target.value })} />
</span>
<ul className="list">
<li>비밀번호는 6~12자의 영문 /소문자, 숫자, 특수문자를 혼합해서 사용하실 있습니다.</li>
<li>쉬운 비밀번호나 자주 쓰는 사이트의 비밀번호가 같을 경우, 도용되기 쉬우므로 주기적으로
변경하셔서 사용하는 것이 좋습니다.</li>
</ul>
<button type="button" onClick={submitFormHandler}><span>회원가입</span></button>
</form>
):(
<Row className="justify-content-end">
<Col xs={12}>
<InfoShareChk/>
</Col>
<Col xs={"auto"}>
<button onClick={infoShareBtn}>동의하기</button>
</Col>
</Row>
)}
</div>
</div>
{/* <!--// 본문 --> */}
</div>
</div>
</div>
</div>
);
}
export default Join;

View File

@ -0,0 +1,171 @@
import * as EgovNet from "../../api/egovFetch";
import React, {useEffect, useState} from "react";
import SbItem from "./SbItem";
import {Col, Row} from "react-bootstrap";
import {VwDiv, VwPtag} from "./Sb.style";
/*최상위 컴포넌트*/
export function Maincontent({docCode}) {
const [doccode, setdoccode] = useState('');
const [docname, setdocname] = useState('');
//TODO 맨처음 목차코드 및 목차제목 안나오는 문제 수정
function setdoccodandname(doccode, docname) {
setdoccode(doccode);
setdocname(docname);
}
return (
<div>
<Col>{doccode+' '+docname}</Col>
<Row>
<CodeTree docCode={docCode} setdoccodandname={setdoccodandname}/>
<CodeCotentData docCode={docCode}/>
</Row>
</div>)
}
/*글 트리 컴포넌트*/
export function CodeTree({docCode, setdoccodandname}) {
const [codeTree, setcodeTree] = useState([]);
const [doccode, setdoccode] = useState(docCode);
let data = [];
const getCodeTree = () => {
EgovNet.requestFetch(
'/standardCode/getCodeTree.do',
{
method: "POST",
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify({})
}, (item) => {
data = item.result.codeTree;
const nest = (menuData, parent_seq = null, link = 'parent_seq') =>
menuData.filter(item => item[link] === parent_seq)
.map(item => ({...item, childrens: nest(menuData, item.seq)}));
setcodeTree(nest(data));
}, () => {
})
}
useEffect(() => {
getCodeTree()
}, [])
return (<Col>
{codeTree.map(resp => {
return (
<SbItem item={resp} openDocCode={doccode} updateDocCode={setdoccodandname}/>
)
}
)}
</Col>)
}
/*글 내용+목차 분리 컴포넌트*/
//TODO 추가 분리 작업 필요
export function CodeCotentData({docCode}) {
const [codeChapter, setcodeChapter] = useState([]);
const [codeContent, setcodeContetn] = useState([]);
let result =[];
const getCodeDetail = (docCode) => {
EgovNet.requestFetch(
'/standardCode/getCodeDetailInfo.do',
{
method: "POST",
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify({
docCode: docCode
})
}, (item) => {
result = item.result.document;
}, () => {
})
}
useEffect(() => {
getCodeDetail(docCode)
setcodeChapter(result.map(item =>{
const isTitle = item.full_content.includes(item.group_title);
return(
<VwDiv depth={item.cont_level} isTitle={isTitle}>
<VwPtag href="#" isTitle={isTitle} onClick={() => {
document.location.hash="#"+item.cont_type_cd;
}} dangerouslySetInnerHTML={ {__html: item.group_title} }>
</VwPtag>
</VwDiv>
)
}))
setcodeContetn(result.map(item =>{
const isTitle = item.full_content.includes(item.group_title);
const docLinkReg = /([A-Z]{3,5}(\s[0-9]{2}){3,4})/g
const docPartReg = /\((?:표|그림|부록)?\s*([A-Z]\.)?(?!\d\))\d+(\.\d+)*(\s?\(\d\))?(-\d+)?(?:\s*[A-Z])?\)/g
if(docLinkReg.test(item.full_content)){
const docCodeAry = item.full_content.match(docLinkReg);
const docCodeMap = new Map();
for(let i=0; i<docCodeAry.length; i++) {
const docCode = docCodeAry[i];
docCodeMap.set(docCode, '<a class="docLink" href="/standardCode/viewer/' + docCode + '" target="_blank">' + docCode + '</a>')
}
docCodeMap.forEach((docCodeLink, docCode)=>{
item.full_content = item.full_content.replaceAll(docCode, docCodeLink);
})
if(docPartReg.test(item.full_content)){
const docPartAry = item.full_content.match(docPartReg);
const docPartMap = new Map();
for(let i=0; i<docPartAry.length; i++) {
const docPart = docPartAry[i];
const docCodeMatch = item.full_content.substring(item.full_content.indexOf(docPart)-30).match(docLinkReg);
let docCode = null;
if (docCodeMatch){
docCode = docCodeMatch[0];
}
//const docCode = item.full_content.substring(item.full_content.indexOf(docPart)-30).match(docLinkReg)[0];
docPartMap.set(docPart, docPart +
'<key class="bookmark" data-doccode="'+docCode+'" data-docpart="'+docPart.replace('(', '').replace(')', '')+'">'+
'<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 16 16" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M2 4a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v11.5a.5.5 0 0 1-.777.416L7 13.101l-4.223 2.815A.5.5 0 0 1 2 15.5V4z"></path><path d="M4.268 1A2 2 0 0 1 6 0h6a2 2 0 0 1 2 2v11.5a.5.5 0 0 1-.777.416L13 13.768V2a1 1 0 0 0-1-1H4.268z"></path></svg>'+
'</key>');
}
docPartMap.forEach((docPartLink, docPart)=>{
item.full_content = item.full_content.replaceAll(docPart, docPartLink);
})
}
if(item.full_content.includes("<table")){
item.full_content = item.full_content.replace('<table ', '<table class="table table-bordered "')
}
if(item.error_cd !== null){
item.full_content = "<div class='errorText'>"+item.error_cd+"</div>"+item.full_content;
}
item.full_content = item.full_content+"<hr>"
return(
<VwDiv depth={item.cont_level} isTitle={isTitle} id={item.cont_type_cd} dangerouslySetInnerHTML={ {__html: item.full_content} }></VwDiv>
)
}
}))
}, [docCode])
return (<>
<CodeChapter codeChapter={codeChapter}/>
<CodeContent codeContent={codeContent}/>
</>)
}
/*글 목차 컴포넌트*/
export function CodeChapter({codeChapter}) {
return (<>{codeChapter}</>)
}
/*글 내용 컴포넌트*/
export function CodeContent({codeContent}) {
return (<>{codeContent}</>)
}

View File

@ -12,6 +12,7 @@ import EgovError from 'components/EgovError';
import EgovMain from 'pages/main/EgovMain';
import EgovLogin from 'pages/login/EgovLogin';
import Join from 'pages/login/join/Join';
//ABOUT
import EgovAboutSite from 'pages/about/EgovAboutSite';
@ -146,9 +147,9 @@ const SecondRoutes = () => {
<Route path={URL.MAIN} element={<EgovMain />} />
{/* LOGIN */}
<Route path={URL.LOGIN} element={<EgovLogin
onChangeLogin={(user) => setLoginVO(user)}
/>}/>
<Route path={URL.LOGIN} element={<EgovLogin onChangeLogin={(user) => setLoginVO(user)}/>}/>
{/*{JOIN}*/}
<Route path={URL.JOIN} element={<Join />} />
{/* ERROR */}
<Route path={URL.ERROR} element={<EgovError />} />

View File

@ -0,0 +1,190 @@
package com.dbnt.kcscbackend.auth;
import com.dbnt.kcscbackend.auth.service.EgovLoginService;
import com.dbnt.kcscbackend.config.common.BaseController;
import com.dbnt.kcscbackend.auth.entity.LoginVO;
import com.dbnt.kcscbackend.config.common.ResponseCode;
import com.dbnt.kcscbackend.config.common.ResultVO;
import com.dbnt.kcscbackend.config.egov.EgovMessageSource;
import com.dbnt.kcscbackend.config.jwt.EgovJwtTokenUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
/**
*
* @author
* @since 2009.03.06
* @version 1.0
* @see
*
* <pre>
* << (Modification Information) >>
*
*
* ------- -------- ---------------------------
* 2009.03.06
* 2011.08.31 JJY 릿
*
* </pre>
*/
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/auth")
@Tag(name="EgovLoginApiController",description = "로그인 관련")
public class EgovLoginApiController extends BaseController {
/** EgovLoginService */
private EgovLoginService loginService;
/** EgovMessageSource */
@Resource(name = "egovMessageSource")
EgovMessageSource egovMessageSource;
/** JWT */
@Autowired
private EgovJwtTokenUtil jwtTokenUtil;
@Operation(
summary = "JWT 로그인",
description = "JWT 로그인 처리",
tags = {"EgovLoginApiController"}
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "로그인 성공"),
@ApiResponse(responseCode = "300", description = "로그인 실패")
})
@PostMapping(value = "/join")
public HashMap<String, Object> actionJoin(@RequestBody LoginVO loginVO, HttpServletRequest request) throws Exception {
HashMap<String, Object> resultMap = new HashMap<String, Object>();
return resultMap;
}
/**
*
* @param loginVO - , LoginVO
* @param request - HttpServletRequest
* @return result - ()
* @exception Exception
*/
@Operation(
summary = "일반 로그인",
description = "일반 로그인 처리",
tags = {"EgovLoginApiController"}
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "로그인 성공"),
@ApiResponse(responseCode = "300", description = "로그인 실패")
})
@PostMapping(value = "/login", consumes = {MediaType.APPLICATION_JSON_VALUE , MediaType.TEXT_HTML_VALUE})
public HashMap<String, Object> actionLogin(@RequestBody LoginVO loginVO, HttpServletRequest request) throws Exception {
HashMap<String,Object> resultMap = new HashMap<String,Object>();
// 1. 일반 로그인 처리
LoginVO loginResultVO = loginService.actionLogin(loginVO);
if (loginResultVO != null && loginResultVO.getId() != null && !loginResultVO.getId().equals("")) {
request.getSession().setAttribute("LoginVO", loginResultVO);
resultMap.put("resultVO", loginResultVO);
resultMap.put("resultCode", "200");
resultMap.put("resultMessage", "성공 !!!");
} else {
resultMap.put("resultVO", loginResultVO);
resultMap.put("resultCode", "300");
resultMap.put("resultMessage", egovMessageSource.getMessage("fail.common.login"));
}
return resultMap;
}
@Operation(
summary = "JWT 로그인",
description = "JWT 로그인 처리",
tags = {"EgovLoginApiController"}
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "로그인 성공"),
@ApiResponse(responseCode = "300", description = "로그인 실패")
})
@PostMapping(value = "/login-jwt")
public HashMap<String, Object> actionLoginJWT(@RequestBody LoginVO loginVO, HttpServletRequest request, ModelMap model) throws Exception {
HashMap<String, Object> resultMap = new HashMap<String, Object>();
// 1. 일반 로그인 처리
LoginVO loginResultVO = loginService.actionLogin(loginVO);
if (loginResultVO != null && loginResultVO.getId() != null && !loginResultVO.getId().equals("")) {
log.debug("===>>> loginVO.getUserSe() = "+loginVO.getUserSe());
log.debug("===>>> loginVO.getId() = "+loginVO.getId());
log.debug("===>>> loginVO.getPassword() = "+loginVO.getPassword());
String jwtToken = jwtTokenUtil.generateToken(loginResultVO);
String username = jwtTokenUtil.getUserSeFromToken(jwtToken);
log.debug("Dec jwtToken username = "+username);
//서버사이드 권한 체크 통과를 위해 삽입
//EgovUserDetailsHelper.isAuthenticated() 가 그 역할 수행. DB에 정보가 없으면 403을 돌려 줌. 로그인으로 튕기는 건 프론트 쪽에서 처리
request.getSession().setAttribute("LoginVO", loginResultVO);
resultMap.put("resultVO", loginResultVO);
resultMap.put("jToken", jwtToken);
resultMap.put("resultCode", "200");
resultMap.put("resultMessage", "성공 !!!");
} else {
resultMap.put("resultVO", loginResultVO);
resultMap.put("resultCode", "300");
resultMap.put("resultMessage", egovMessageSource.getMessage("fail.common.login"));
}
return resultMap;
}
/**
* .
* @return resultVO
* @exception Exception
*/
@Operation(
summary = "로그아웃",
description = "로그아웃 처리(JWT,일반 관계 없이)",
tags = {"EgovLoginApiController"}
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "로그아웃 성공"),
})
@GetMapping(value = "/logout")
public ResultVO actionLogoutJSON(HttpServletRequest request, HttpServletResponse response) throws Exception {
ResultVO resultVO = new ResultVO();
new SecurityContextLogoutHandler().logout(request, response, null);
resultVO.setResultCode(ResponseCode.SUCCESS.getCode());
resultVO.setResultMessage(ResponseCode.SUCCESS.getMessage());
return resultVO;
}
}

View File

@ -1,4 +1,4 @@
package com.dbnt.kcscbackend.config.common;
package com.dbnt.kcscbackend.auth.entity;
import io.swagger.v3.oas.annotations.media.Schema;
@ -38,7 +38,7 @@ public class LoginVO implements Serializable{
@Schema(description = "이름")
private String name;
@Schema(description = "주민등록번호")
private String ihidNum;

View File

@ -0,0 +1,56 @@
package com.dbnt.kcscbackend.auth.mapper;
import com.dbnt.kcscbackend.auth.entity.LoginVO;
import org.apache.ibatis.annotations.Mapper;
/**
*
* @author
* @since 2009.03.06
* @version 1.0
* @see
*
* <pre>
* << (Modification Information) >>
*
*
* ------- -------- ---------------------------
* 2009.03.06
* 2011.08.31 JJY 릿
*
* </pre>
*/
@Mapper
public interface LoginUserMapper {
/**
*
* @param vo LoginVO
* @return LoginVO
* @exception Exception
*/
LoginVO actionLogin(LoginVO vo);
/**
* .
* @param vo LoginVO
* @return LoginVO
* @exception Exception
*/
LoginVO searchId(LoginVO vo);
/**
* .
* @param vo LoginVO
* @return LoginVO
* @exception Exception
*/
LoginVO searchPassword(LoginVO vo);
/**
* .
* @param vo LoginVO
* @exception Exception
*/
void updatePassword(LoginVO vo);
}

View File

@ -0,0 +1,52 @@
package com.dbnt.kcscbackend.auth.service;
import com.dbnt.kcscbackend.auth.entity.LoginVO;
/**
*
* @author
* @since 2009.03.06
* @version 1.0
* @see
*
* <pre>
* << (Modification Information) >>
*
*
* ------- -------- ---------------------------
* 2009.03.06
* 2011.08.31 JJY 릿
*
* </pre>
*/
public interface EgovLoginService {
/**
*
* @return LoginVO
*
* @param vo LoginVO
* @exception Exception Exception
*/
public LoginVO actionLogin(LoginVO vo) throws Exception;
/**
* .
* @return LoginVO
*
* @param vo LoginVO
* @exception Exception Exception
*/
public LoginVO searchId(LoginVO vo) throws Exception;
/**
* .
* @return boolean
*
* @param vo LoginVO
* @exception Exception Exception
*/
public boolean searchPassword(LoginVO vo) throws Exception;
}

View File

@ -0,0 +1,125 @@
package com.dbnt.kcscbackend.auth.service.impl;
import com.dbnt.kcscbackend.auth.mapper.LoginUserMapper;
import com.dbnt.kcscbackend.auth.service.EgovLoginService;
import com.dbnt.kcscbackend.auth.entity.LoginVO;
import com.dbnt.kcscbackend.config.egov.EgovFileScrty;
import com.dbnt.kcscbackend.config.util.EgovNumberUtil;
import com.dbnt.kcscbackend.config.util.EgovStringUtil;
import lombok.RequiredArgsConstructor;
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
*
* @author
* @since 2009.03.06
* @version 1.0
* @see
*
* <pre>
* << (Modification Information) >>
*
*
* ------- -------- ---------------------------
* 2009.03.06
* 2011.08.31 JJY 릿
*
* </pre>
*/
@Service
@Transactional
@RequiredArgsConstructor
public class EgovLoginServiceImpl extends EgovAbstractServiceImpl implements EgovLoginService {
private final LoginUserMapper loginMapper;
/**
*
* @param vo LoginVO
* @return LoginVO
* @exception Exception
*/
@Override
public LoginVO actionLogin(LoginVO vo) throws Exception {
// 1. 입력한 비밀번호를 암호화한다.
String enpassword = EgovFileScrty.encryptPassword(vo.getPassword(), vo.getId());
vo.setPassword(enpassword);
// 2. 아이디와 암호화된 비밀번호가 DB와 일치하는지 확인한다.
LoginVO loginVO = loginMapper.actionLogin(vo);
// 3. 결과를 리턴한다.
if (loginVO != null && !loginVO.getId().equals("") && !loginVO.getPassword().equals("")) {
return loginVO;
} else {
loginVO = new LoginVO();
}
return loginVO;
}
/**
* .
* @param vo LoginVO
* @return LoginVO
* @exception Exception
*/
@Override
public LoginVO searchId(LoginVO vo) throws Exception {
// 1. 이름, 이메일주소가 DB와 일치하는 사용자 ID를 조회한다.
LoginVO loginVO = loginMapper.searchId(vo);
// 2. 결과를 리턴한다.
if (loginVO != null && !loginVO.getId().equals("")) {
return loginVO;
} else {
loginVO = new LoginVO();
}
return loginVO;
}
/**
* .
* @param vo LoginVO
* @return boolean
* @exception Exception
*/
@Override
public boolean searchPassword(LoginVO vo) throws Exception {
boolean result = true;
// 1. 아이디, 이름, 이메일주소, 비밀번호 힌트, 비밀번호 정답이 DB와 일치하는 사용자 Password를 조회한다.
LoginVO loginVO = loginMapper.searchPassword(vo);
if (loginVO == null || loginVO.getPassword() == null || loginVO.getPassword().equals("")) {
return false;
}
// 2. 임시 비밀번호를 생성한다.(영+영+숫+영+영+숫=6자리)
String newpassword = "";
for (int i = 1; i <= 6; i++) {
// 영자
if (i % 3 != 0) {
newpassword += EgovStringUtil.getRandomStr('a', 'z');
// 숫자
} else {
newpassword += EgovNumberUtil.getRandomNum(0, 9);
}
}
// 3. 임시 비밀번호를 암호화하여 DB에 저장한다.
LoginVO pwVO = new LoginVO();
String enpassword = EgovFileScrty.encryptPassword(newpassword, vo.getId());
pwVO.setId(vo.getId());
pwVO.setPassword(enpassword);
pwVO.setUserSe(vo.getUserSe());
loginMapper.updatePassword(pwVO);
return result;
}
}

View File

@ -0,0 +1,305 @@
package com.dbnt.kcscbackend.config.egov;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.codec.binary.Base64;
import java.io.*;
import java.security.MessageDigest;
/**
* Base64/ / Business Interface class
* @author
* @since 2009.01.19
* @version 1.0
* @see
*
* <pre>
* << (Modification Information) >>
*
*
* ------- -------- ---------------------------
* 2009.01.19
* 2011.08.31 JJY 릿
*
* </pre>
*/
@Slf4j
public class EgovFileScrty {
// 파일구분자
static final String FILE_SEPARATOR = System.getProperty("file.separator");
// 버퍼사이즈
static final int BUFFER_SIZE = 1024;
/**
*
*
* @param source
* @param target
* @return boolean result True/False
* @exception Exception
*/
public static boolean encryptFile(String source, String target) throws Exception {
// 암호화 여부
boolean result = false;
String sourceFile = EgovWebUtil.filePathBlackList(source.replace("\\", FILE_SEPARATOR).replace("/", FILE_SEPARATOR));
String targetFile = EgovWebUtil.filePathBlackList(target.replace("\\", FILE_SEPARATOR).replace("/", FILE_SEPARATOR));
File srcFile = new File(sourceFile);
BufferedInputStream input = null;
BufferedOutputStream output = null;
byte[] buffer = new byte[BUFFER_SIZE];
try {
if (srcFile.exists() && srcFile.isFile()) {
input = new BufferedInputStream(new FileInputStream(srcFile));
output = new BufferedOutputStream(new FileOutputStream(targetFile));
int length = 0;
while ((length = input.read(buffer)) >= 0) {
byte[] data = new byte[length];
System.arraycopy(buffer, 0, data, 0, length);
output.write(encodeBinary(data).getBytes());
output.write(System.getProperty("line.separator").getBytes());
}
result = true;
}
} finally {
if (input != null) {
try {
input.close();
} catch (IOException ignore) {
log.debug("IGNORE: {}", ignore);
}
}
if (output != null) {
try {
output.close();
} catch (IOException ignore) {
log.debug("IGNORE: {}", ignore);
}
}
}
return result;
}
/**
*
*
* @param source
* @param target
* @return boolean result True/False
* @exception Exception
*/
public static boolean decryptFile(String source, String target) throws Exception {
// 복호화 여부
boolean result = false;
String sourceFile = source.replace("\\", FILE_SEPARATOR).replace("/", FILE_SEPARATOR);
String targetFile = target.replace("\\", FILE_SEPARATOR).replace("/", FILE_SEPARATOR);
File srcFile = new File(sourceFile);
BufferedReader input = null;
BufferedOutputStream output = null;
//byte[] buffer = new byte[BUFFER_SIZE];
String line = null;
try {
if (srcFile.exists() && srcFile.isFile()) {
input = new BufferedReader(new InputStreamReader(new FileInputStream(srcFile)));
output = new BufferedOutputStream(new FileOutputStream(targetFile));
while ((line = input.readLine()) != null) {
byte[] data = line.getBytes();
output.write(decodeBinary(new String(data)));
}
result = true;
}
} finally {
if (input != null) {
try {
input.close();
} catch (IOException ignore) {
log.debug("IGNORE: {}", ignore);
}
}
if (output != null) {
try {
output.close();
} catch (IOException ignore) {
log.debug("IGNORE: {}", ignore);
}
}
}
return result;
}
/**
*
*
* @param data
* @return String result
* @exception Exception
*/
public static String encodeBinary(byte[] data) throws Exception {
if (data == null) {
return "";
}
return new String(Base64.encodeBase64(data));
}
/**
*
*
* @param data
* @return String result
* @exception Exception
*/
public static String encode(String data) throws Exception {
return encodeBinary(data.getBytes());
}
/**
*
*
* @param data
* @return String result
* @exception Exception
*/
public static byte[] decodeBinary(String data) throws Exception {
return Base64.decodeBase64(data.getBytes());
}
/**
*
*
* @param String data
* @return String result
* @exception Exception
*/
public static String decode(String data) throws Exception {
return new String(decodeBinary(data));
}
/**
* ( SHA-256 ).
*
* deprecated : salt ID encryptPassword(password, id)
*
* @param data
* @return String result
* @exception Exception
*/
@Deprecated
public static String encryptPassword(String data) throws Exception {
if (data == null) {
return "";
}
byte[] plainText = null; // 평문
byte[] hashValue = null; // 해쉬값
plainText = data.getBytes();
MessageDigest md = MessageDigest.getInstance("SHA-256");
// 변경 시 기존 hash 값에 검증 불가.. => deprecated 시키고 유지
/*
// Random 방식의 salt 추가
SecureRandom ng = new SecureRandom();
byte[] randomBytes = new byte[16];
ng.nextBytes(randomBytes);
md.reset();
md.update(randomBytes);
*/
hashValue = md.digest(plainText);
/*
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(hashValue);
*/
return new String(Base64.encodeBase64(hashValue));
}
/**
* ( SHA-256 )
*
* @param password
* @param id salt ID
* @return
* @throws Exception
*/
public static String encryptPassword(String password, String id) throws Exception {
if (password == null) {
return "";
}
byte[] hashValue = null; // 해쉬값
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.reset();
md.update(id.getBytes());
hashValue = md.digest(password.getBytes());
return new String(Base64.encodeBase64(hashValue));
}
/**
* ( SHA-256 )
* @param data
* @param salt Salt
* @return
* @throws Exception
*/
public static String encryptPassword(String data, byte[] salt) throws Exception {
if (data == null) {
return "";
}
byte[] hashValue = null; // 해쉬값
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.reset();
md.update(salt);
hashValue = md.digest(data.getBytes());
return new String(Base64.encodeBase64(hashValue));
}
/**
* (salt ).
*
* @param data
* @param encoded (Base64 )
* @return
* @throws Exception
*/
public static boolean checkPassword(String data, String encoded, byte[] salt) throws Exception {
byte[] hashValue = null; // 해쉬값
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.reset();
md.update(salt);
hashValue = md.digest(data.getBytes());
return MessageDigest.isEqual(hashValue, Base64.decodeBase64(encoded.getBytes()));
}
}

View File

@ -2,7 +2,7 @@ package com.dbnt.kcscbackend.config.jwt;
import com.dbnt.kcscbackend.config.egov.EgovProperties;
import com.dbnt.kcscbackend.config.common.LoginVO;
import com.dbnt.kcscbackend.auth.entity.LoginVO;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

View File

@ -1,6 +1,6 @@
package com.dbnt.kcscbackend.config.jwt;
import com.dbnt.kcscbackend.config.common.LoginVO;
import com.dbnt.kcscbackend.auth.entity.LoginVO;
import com.dbnt.kcscbackend.config.util.EgovStringUtil;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;

View File

@ -1,6 +1,6 @@
package com.dbnt.kcscbackend.config.security;
import com.dbnt.kcscbackend.config.common.LoginVO;
import com.dbnt.kcscbackend.auth.entity.LoginVO;
import org.springframework.core.MethodParameter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;

View File

@ -44,6 +44,7 @@ public class SecurityConfig {
"/login/**",
"/auth/login-jwt",//JWT 로그인
"/auth/login",//일반 로그인
"/auth/join",//회원가입
"/cmm/main/**.do", // 메인페이지
"/cmm/fms/FileDown.do", //파일 다운로드
"/cmm/fms/getImage.do", //갤러리 이미지보기

View File

@ -1,7 +1,7 @@
package com.dbnt.kcscbackend.standardCode;
import com.dbnt.kcscbackend.config.common.BaseController;
import com.dbnt.kcscbackend.config.common.LoginVO;
import com.dbnt.kcscbackend.auth.entity.LoginVO;
import com.dbnt.kcscbackend.config.common.ResponseCode;
import com.dbnt.kcscbackend.config.common.ResultVO;
import com.dbnt.kcscbackend.standardCode.entity.TnDocumentCodeList;

View File

@ -0,0 +1,182 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dbnt.kcscbackend.auth.mapper.LoginUserMapper">
<!-- 일반 로그인 -->
<select id="actionLogin" resultType="LoginVO" parameterType="LoginVO">
<!-- 일반회원 -->
<if test="userSe == &quot;USR&quot;">
SELECT EMPLYR_ID AS id
, USER_NM AS name
, PASSWORD AS password
, IHIDNUM AS ihidNum
, EMAIL_ADRES AS email
, 'USR' AS userSe
, ORGNZT_ID AS orgnztId
, ESNTL_ID AS uniqId
FROM LETTNEMPLYRINFO
WHERE EMPLYR_ID = #{id}
AND PASSWORD = #{password}
AND EMPLYR_STTUS_CODE = 'P'
</if>
<!-- 기업회원 -->
<if test="userSe == &quot;ENT&quot;">
SELECT entrprsmber_id AS id
, cmpny_nm AS name
, entrprs_mber_password AS password
, bizrno AS ihidNum
, applcnt_email_adres AS email
, 'ENT' AS userSe
, '-' AS orgnztId
, esntl_id AS uniqId
FROM LETTNENTRPRSMBER
WHERE entrprsmber_id = #{id}
AND entrprs_mber_password = #{password}
AND entrprs_mber_sttus = 'P'
</if>
<!-- 업무사용자 -->
<if test="userSe == &quot;USR&quot;">
SELECT emplyr_id AS id
, user_nm AS name
, password AS password
, ihidnum AS ihidNum
, email_adres AS email
, 'USR' AS userSe
, orgnzt_id AS orgnztId
, esntl_id AS uniqId
FROM LETTNEMPLYRINFO
WHERE emplyr_id = #{id}
AND password = #{password}
AND emplyr_sttus_code = 'P'
</if>
</select>
<!-- 인증서 로그인 -->
<select id="actionCrtfctLogin" resultType="LoginVO" parameterType="LoginVO">
SELECT emplyr_id AS id
, user_nm AS name
, password AS password
, ihidnum AS ihidNum
, email_adres AS email
, 'USR' AS userSe
, orgnzt_id AS orgnztId
, esntl_id AS uniqId
FROM LETTNEMPLYRINFO
WHERE sub_dn = #{dn}
</select>
<!-- 아이디 찾기 -->
<select id="searchId" resultType="LoginVO" parameterType="LoginVO">
<!-- 일반회원 -->
<if test="userSe == &quot;GNR&quot;">
SELECT mber_id AS id
FROM LETTNGNRLMBER
WHERE mber_nm = #{name}
AND mber_email_adres = #{email}
AND mber_sttus = 'P'
</if>
<!-- 기업회원 -->
<if test="userSe == &quot;ENT&quot;">
SELECT entrprsmber_id AS id
FROM LETTNENTRPRSMBER
WHERE cmpny_nm = #{name}
AND applcnt_email_adres = #{email}
AND entrprs_mber_sttus = 'P'
</if>
<!-- 업무사용자 -->
<if test="userSe == &quot;USR&quot;">
SELECT emplyr_id AS id
FROM LETTNEMPLYRINFO
WHERE user_nm = #{name}
AND email_adres = #{email}
AND emplyr_sttus_code = 'P'
</if>
</select>
<!-- 비밀번호 찾기 -->
<select id="searchPassword" resultType="LoginVO" parameterType="LoginVO">
<!-- 일반회원 -->
<if test="userSe == &quot;GNR&quot;">
SELECT password AS password
FROM LETTNGNRLMBER
WHERE mber_id = #{id}
AND mber_nm = #{name}
AND mber_email_adres = #{email}
AND password_hint = #{passwordHint}
AND password_cnsr = #{passwordCnsr}
AND mber_sttus = 'P'
</if>
<!-- 기업회원 -->
<if test="userSe == &quot;ENT&quot;">
SELECT entrprs_mber_password AS password
FROM LETTNENTRPRSMBER
WHERE entrprsmber_id = #{id}
AND cmpny_nm = #{name}
AND applcnt_email_adres = #{email}
AND entrprs_mber_password_hint = #{passwordHint}
AND entrprs_mber_password_cnsr = #{passwordCnsr}
AND entrprs_mber_sttus = 'P'
</if>
<!-- 업무사용자 -->
<if test="userSe == &quot;USR&quot;">
SELECT password AS password
FROM LETTNEMPLYRINFO
WHERE emplyr_id = #{id}
AND user_nm = #{name}
AND email_adres = #{email}
AND password_hint = #{passwordHint}
AND password_cnsr = #{passwordCnsr}
AND emplyr_sttus_code = 'P'
</if>
</select>
<!-- 변경된 비밀번호를 저장 -->
<update id="updatePassword" parameterType="LoginVO">
<!-- 일반회원 -->
<if test="userSe == &quot;GNR&quot;">
UPDATE LETTNGNRLMBER
SET password = #{password}
WHERE mber_id = #{id}
</if>
<!-- 기업회원 -->
<if test="userSe == &quot;ENT&quot;">
UPDATE LETTNENTRPRSMBER
SET entrprs_mber_password = #{password}
WHERE entrprsmber_id = #{id}
</if>
<!-- 업무사용자 -->
<if test="userSe == &quot;USR&quot;">
UPDATE LETTNEMPLYRINFO
SET password = #{password}
WHERE emplyr_id = #{id}
</if>
</update>
</mapper>