diff --git a/frontend/src/assets/css/style.css b/frontend/src/assets/css/style.css index a1cc407..5bcd107 100644 --- a/frontend/src/assets/css/style.css +++ b/frontend/src/assets/css/style.css @@ -1806,4 +1806,85 @@ textarea:focus{ .datepicker .datepicker-calender .click a, .datepicker .datepicker-calender .today a{color: #fff;} .datepicker .datepicker-calender .disabled a{color:#c9c9c9 !important;} -/*# sourceMappingURL=style.css.map */ \ No newline at end of file +/*# sourceMappingURL=style.css.map */ + +/* loadingPage */ +.loadingDimmed { + width: 100vw; + height: 100vh; + position: fixed; + top: 0; + left: 0; + background: rgba(0, 0, 0, 0.2); + z-index: 400; +} +.sp-3balls, +.sp-3balls:before, +.sp-3balls:after { + display: inline-block; + width: 18px; + height: 18px; + background: #946dc2; + border-radius: 100%; +} +.sp-3balls { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 1000; + opacity: 1; + animation: spScaleAlpha 2s infinite linear; +} +.sp-3balls:before, +.sp-3balls:after { + content: ""; + position: absolute; + opacity: 0.25; +} +.sp-3balls:before { + right: 30px; + animation: spScaleAlphaBefore 2s infinite linear; +} +.sp-3balls:after { + left: 30px; + animation: spScaleAlphaAfter 2s infinite linear; +} + +@keyframes spScaleAlpha { + 0% { + opacity: 1; + } + 33% { + opacity: 0.25; + } + 66% { + opacity: 0.25; + } + 100% { + opacity: 1; + } +} + +@keyframes spScaleAlphaBefore { + 33% { + opacity: 0.25; + } + 66% { + opacity: 1; + } + 100% { + opacity: 0.25; + } +} +@keyframes spScaleAlphaAfter { + 0% { + opacity: 0.25; + } + 33% { + opacity: 1; + } + 66% { + opacity: 0.25; + } +} \ No newline at end of file diff --git a/frontend/src/common/config.js b/frontend/src/common/config.js index da36c93..d812e25 100644 --- a/frontend/src/common/config.js +++ b/frontend/src/common/config.js @@ -7,7 +7,8 @@ const consts = { tokenPart1: 'JwtPart1', tokenPart2: 'JwtPart2', tokenPart3: 'JwtPart3', - tokenPart4: 'JwtPart4' + tokenPart4: 'JwtPart4', + authToken: null } export { environment, testProp, consts }; diff --git a/frontend/src/common/http-client.js b/frontend/src/common/http-client.js index 99f214d..e661b66 100644 --- a/frontend/src/common/http-client.js +++ b/frontend/src/common/http-client.js @@ -20,7 +20,8 @@ const authInterceptor = config => { // cookie, header 등에 자격정보 설정이 필요한 api는 true 설정으로 호출해야 하고 // 자격정보 설정이 필요없는 api는 withCredentials=false 설정으로 호출해야 한다. // config.withCredentials = !config.url.startsWith('/api/public/'); - console.log("Test Url : "+ config.url); + // console.log("Test Url : "+ config.url); + /*if(config.url == '/api/auth/login'){ config.baseURL = "http://localhost:3000"; }*/ @@ -28,17 +29,27 @@ const authInterceptor = config => { }; const tokenInterceptor = config => { - if(tokenSvc.getAuthorization(consts.tokenPart1) != null){ - config.headers.Authorization = tokenSvc.getAuthorization(consts.tokenPart1); - //console.log("getToken : "+config.headers.Authorization); - //config.headers.Authorization = tokenSvc.getToken(); + // var authToken = tokenSvc.getAuthToken(); + // console.log(this.$store); + // alert('tokenInterceptor'+authToken); + + // if(tokenSvc.getAuthorization(consts.tokenPart1) != null){ + // //config.headers.Authorization = tokenSvc.getAuthorization(consts.tokenPart1); + // //console.log("getToken : "+config.headers.Authorization); + // //config.headers.Authorization = tokenSvc.getToken(); + // } + + console.log('http : ',sessionStorage.authToken); + if(sessionStorage.authToken != null) { + config.headers.Authorization = 'Bearer '+ sessionStorage.authToken; } + return config; } const loggerInterceptor = config => { //console.log('testProp:', testProp); - //console.log('request url:', config.url, 'params:', config.data); + console.log('request url:', config.url, 'params:', config.data, 'config: ', config); return config; }; @@ -48,46 +59,65 @@ const loadingLayer = (type, config) => { get: httpClient.get(url, { params: { ... }, headers: {"show-layer": "Yes"} }) // in 2nd property post: httpClient.post(url, params, { headers: {"show-layer": "Yes"} }) // 3rd property */ - if (config.headers['Show-Layer'] == 'Yes') { - if (type) { - loadOverlap.push('add'); - } else { - loadOverlap.pop(); - } + + // if (config.headers['Show-Layer'] == 'Yes') { + // if (type) { + // loadOverlap.push('add'); + // } else { + // loadOverlap.pop(); + // } - if (loadOverlap.length > 0) { - document.querySelector('html > body').style.overflow = 'hidden'; // 스크롤 block - document.getElementsByClassName('loading_layer')[0].style.display = 'block'; - } else { - document.querySelector('html > body').style.removeProperty('overflow'); // 스크롤 allow - document.getElementsByClassName('loading_layer')[0].style.display = 'none'; - } + // if (loadOverlap.length > 0) { + // document.querySelector('html > body').style.overflow = 'hidden'; // 스크롤 block + // document.getElementsByClassName('loading_layer')[0].style.display = 'block'; + // } else { + // document.querySelector('html > body').style.removeProperty('overflow'); // 스크롤 allow + // document.getElementsByClassName('loading_layer')[0].style.display = 'none'; + // } + // } + if(type){ + document.querySelector('html > body').style.overflow = 'hidden'; // 스크롤 block + document.getElementsByClassName('loadingDimmed')[0].style.display = 'block'; + document.getElementsByClassName('sp-3balls')[0].style.display = 'block'; + }else{ + document.querySelector('html > body').style.removeProperty('overflow'); // 스크롤 allow + document.getElementsByClassName('loadingDimmed')[0].style.display = 'none'; + document.getElementsByClassName('sp-3balls')[0].style.display = 'none'; } }; -/*const loadingLayerInterceptor = config => { +const urlInterceptor = config => { + if(sessionStorage.fromUrl != null){ + config.params.fromUrl = sessionStorage.fromUrl; + } + return config; +} +const loadingLayerInterceptor = config => { loadingLayer(true, config); return config; -};*/ +}; /** Adding the request interceptors */ httpClient.interceptors.request.use(authInterceptor); httpClient.interceptors.request.use(tokenInterceptor); +httpClient.interceptors.request.use(urlInterceptor); httpClient.interceptors.request.use(loggerInterceptor); -//httpClient.interceptors.request.use(loadingLayerInterceptor); +httpClient.interceptors.request.use(loadingLayerInterceptor); /** Adding the response interceptors */ httpClient.interceptors.response.use( response => { - //loadingLayer(false, response.config); + loadingLayer(false, response.config); console.log('response status:', response.status, 'data:', response.data); return response; }, error => { console.log(error); + //alert(error); + delete sessionStorage.authToken; if(error.message === 'Network Error'){ alert('네트워크 오류가 발생했습니다. 잠시 후 다시 시도해주세요.'); - tokenSvc.removeToken(); + // tokenSvc.removeToken(); window.top.location.href = '/login'; } if (error.response != undefined && error.response != null) loadingLayer(false, error.response.config); @@ -98,17 +128,18 @@ httpClient.interceptors.response.use( return Promise.reject(error); }else if(error.response.status == 401){ alert('세션이 만료되었습니다.'); - tokenSvc.removeToken(); + // tokenSvc.removeToken(); window.top.location.href = '/login'; } else if (error.response.status == 418) { - tokenSvc.removeToken(); + // tokenSvc.removeToken(); + alert('세션이 만료되었습니다.'); window.top.location.href = '/login'; }else if (error.response.status == 500) { if (error.response.data != null && error.response.data.message == '511 NETWORK_AUTHENTICATION_REQUIRED') { alert('웹템플릿 IP가 브랜드포털에 등록이 필요합니다. 기술지원에 문의해주세요.'); return Promise.reject(error); } else { - tokenSvc.removeToken(); + // tokenSvc.removeToken(); window.top.location.href = '/login'; } } else if (error.response.status == 511) { @@ -120,7 +151,7 @@ httpClient.interceptors.response.use( // return Promise.reject(error); // } else { - alert("else"); + // alert("else"); console.log('response error:', error); return Promise.reject(error); } diff --git a/frontend/src/common/token-service.js b/frontend/src/common/token-service.js index 6b2e604..f3d570e 100644 --- a/frontend/src/common/token-service.js +++ b/frontend/src/common/token-service.js @@ -1,53 +1,28 @@ import * as utils from './utils'; -import { consts } from './config'; - - -// const KEY_TOKEN = 'access_token'; const tokenSvc = { getToken() { - // return store.getters['login/getToken']; - // var payload = sessionStorage.getItem(KEY_TOKEN); - var jwtPart1 = utils.getCookie(consts.tokenPart1); - if (!jwtPart1) - return null; - var payload = utils.base64decode(jwtPart1.split('.').pop()); - return JSON.parse(payload); - }, - removeToken() { - var tokenNm1 = consts.tokenPart1; - var tokenNm2 = consts.tokenPart2; - var tokenNm3 = consts.tokenPart3; - var tokenNm4 = consts.tokenPart4; - - document.cookie = tokenNm1+'=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'; - document.cookie = tokenNm2+'=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'; - document.cookie = tokenNm3+'=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'; - document.cookie = tokenNm4+'=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'; - }, - getAuthorization(tokenKey){ - var jwtPart = utils.getCookie(tokenKey); - if(!jwtPart){ + var authToken = sessionStorage.getItem('authToken'); + if(authToken == null){ return null; } - return jwtPart; + + var tokenArr = authToken.split('.'); + + var jwtToken = utils.base64decode(tokenArr[1]); + return JSON.parse(jwtToken); }, - - // saveToken(jwtPart1) { - // if (!jwtPart1) - // return; - - // var payload = utils.base64decode(jwtPart1.split('.').pop()); - // console.log('save token:', payload); - // // store.commit('login/saveToken', token); - // sessionStorage.setItem(KEY_TOKEN, payload); - // }, - - // removeToken() { - // // store.commit('login/removeToken'); - // sessionStorage.removeItem(KEY_TOKEN); - // } - + removeToken() { + delete sessionStorage.authToken; + }, + + setAuthToken(token){ + sessionStorage.authToken = token; + }, + getAuthToken(){ + var authToken = sessionStorage.authToken; + return authToken; + } }; export default tokenSvc; diff --git a/frontend/src/common/vue-mixins.js b/frontend/src/common/vue-mixins.js index 5425e79..7bc2b7a 100644 --- a/frontend/src/common/vue-mixins.js +++ b/frontend/src/common/vue-mixins.js @@ -46,7 +46,7 @@ const coreUiMixin = { */ openLayer: function(layerId) { if (layerId == undefined || layerId == null || layerId == '') { - alert('layerId를 설정해 주세요.'); + // alert('layerId를 설정해 주세요.'); } else { this.$emit('changeLayerId', layerId); } diff --git a/frontend/src/components/CustomGrid.vue b/frontend/src/components/CustomGrid.vue index 5038abd..ddbe860 100644 --- a/frontend/src/components/CustomGrid.vue +++ b/frontend/src/components/CustomGrid.vue @@ -33,7 +33,8 @@ export default { 'addTableStyle', // 추가되는 그리드 style 'addTbCls', // 추가되는 테이블 클래스 'addCls', // 추가되는 클래스 - 'totalItems' // 부모창에 표시할 총 컨텐츠 개수 변수 명 (더 좋은 방법 있으면 알려주세요.) + 'totalItems', // 부모창에 표시할 총 컨텐츠 개수 변수 명 (더 좋은 방법 있으면 알려주세요.) + 'pageRange', // next or pre 이동할 페이지 단위 ], components: { VRuntimeTemplate @@ -57,11 +58,13 @@ export default { colsData: '', headerData: '', bodyData: '', - pagingData: '' + pagingData: '', + nextPageRange: 1 + }; }, created() { - this.cleanData(); + this.cleanData(); if (this.initialRequest == true) { this.readData(); } @@ -105,6 +108,7 @@ export default { this.headerData = ''; this.bodyData = ''; this.pagingData = ''; + this.nextPageRange = 1; }, readData(isKeep) { if (typeof this.url != undefined && this.url != null && this.url != '') { @@ -126,13 +130,11 @@ export default { let pageStr = ''; var vm = this; - console.log("grid url >> " + this.url); httpClient //.get(this.url, { params: this.getParams, headers: { 'Show-Layer': 'Yes' }}) .post(this.url, this.getParams, {headers: { 'Show-Layer': 'Yes' }}) .then(response => { let resp = response.data; - console.log(resp); //if (resp != null && resp.result == true) { if (resp != null && resp.retCode == '0000') { let data = resp.data; @@ -149,10 +151,13 @@ export default { vm.totalCount = data.pagination.totalCount; pageStr = vm.makePagingView(); }*/ - if (vm.pagination == true) { + if (vm.pagination == true) { vm.currentIndex = data.paging.page == 0 ? 1 : data.paging.page; vm.totalCount = data.paging.totalCnt; pageStr = vm.makePagingView(); + if(typeof this.pageRange != 'undefined'){ + vm.nextPageRange = this.pageRange; + } } // 조회결과 없음. }else if(resp != null && resp.retCode == '1004'){ @@ -536,55 +541,9 @@ export default { str = ''; } return str; - }, - /* + }, + makePagingView() { - let pData = '
'; - - let totalPage = Math.ceil(this.totalCount / this.curPerPage); - if (totalPage < 1) { - totalPage = 1; - } - let pageGroup = Math.ceil(this.currentIndex / this.pageCount); - - let last = pageGroup * this.pageCount; - if (last > totalPage) { - last = totalPage; - } - - let first = last - (this.pageCount - 1); - if (first < 1) { - first = 1; - } - let prev = first - 1; - if (prev < 1) { - prev = 1; - } - let next = last + 1; - if (next > totalPage) { - next = totalPage; - } - - pData += '처음으로'; - pData += ''; - - for (var i = first; i <= last; i++) { - let actCls = ''; - if (i == this.currentIndex) { - actCls = "class='active'"; - } - pData += '' + i + ''; - } - - pData += ''; - pData += - '마지막으로'; - - pData += '
'; - return pData; - }, - */ - makePagingView() { let pData = ' \ No newline at end of file + diff --git a/src/main/java/kr/co/uplus/ez/api/login/LoginController.java b/src/main/java/kr/co/uplus/ez/api/login/LoginController.java index 904a904..eafc100 100644 --- a/src/main/java/kr/co/uplus/ez/api/login/LoginController.java +++ b/src/main/java/kr/co/uplus/ez/api/login/LoginController.java @@ -143,9 +143,10 @@ public class LoginController { if (authNumDto.getOprtrId().contains(authenticationWithoutId.get(i).trim())) { loginSuccessHandler.process(request, response, authentication); - // access 토큰 생성 - jwtSvc.generatePrivateToken(response, authentication); - + // Auth 토큰 생성 + String authToken = jwtSvc.generatePrivateToken(response, authentication); +// jwtSvc.generatePrivateToken(response, authentication); + String nextUrl = getReturnUrl(request, response); logger.debug("login SUCCESS - nextUrl = [{}]", nextUrl); AuthUser authUser = loginService.getUser(authentication.getName()); @@ -157,10 +158,27 @@ public class LoginController { Menu rootMenu = menu.get(0); String rootUrl = rootMenu.getChildren().get(0).getMenuUrl(); + ArrayList menuUrls = new ArrayList(); + for(int j=0; j< menu.size(); j++) { + Menu parentMenu = menu.get(j); + if(parentMenu.getChildren().size() > 0) { + ArrayList childrenMenu = (ArrayList)parentMenu.getChildren(); + for(int k=0; k< childrenMenu.size(); k++) { + Menu childMenu = childrenMenu.get(k); + String menuUrl = childMenu.getMenuUrl(); + menuUrls.add(menuUrl); + } + } + //String menuUrl = rootMenu.getChildren().get(j).getMenuUrl(); +// menuUrls.add(menuUrl); + } + LoginRes loginRes = new LoginRes(rootUrl); loginRes.setUserId(authUser.getOprtrId()); loginRes.setUserNm(authUser.getOprtrNm()); - + loginRes.setMenuUrls(menuUrls); + loginRes.setAuthToken(authToken); + ChkAuthNumResDto result = new ChkAuthNumResDto(ApiResponseCode.SUCCESS, loginRes); return result; } @@ -172,10 +190,11 @@ public class LoginController { // 2차인증후 시큐리티 성공핸들러 rCode = loginSuccessHandler.process(request, response, authentication); - - // access 토큰 생성 - jwtSvc.generatePrivateToken(response, authentication); - + + // loginToken + String authToken = jwtSvc.generatePrivateToken(response, authentication); +// jwtSvc.generatePrivateToken(response, authentication); + String nextUrl = getReturnUrl(request, response); logger.debug("login SUCCESS - nextUrl = [{}]", nextUrl); AuthUser authUser = loginService.getUser(authentication.getName()); @@ -187,11 +206,26 @@ public class LoginController { Menu rootMenu = menu.get(0); String rootUrl = rootMenu.getChildren().get(0).getMenuUrl(); + // 메뉴 url data 생성 + ArrayList menuUrls = new ArrayList(); + for(int j=0; j< menu.size(); j++) { + Menu parentMenu = menu.get(j); + if(parentMenu.getChildren().size() > 0) { + ArrayList childrenMenu = (ArrayList)parentMenu.getChildren(); + for(int k=0; k< childrenMenu.size(); k++) { + Menu childMenu = childrenMenu.get(k); + String menuUrl = childMenu.getMenuUrl(); + menuUrls.add(menuUrl); + } + } + } + LoginRes loginRes = new LoginRes(rootUrl); - //LoginRes loginRes = new LoginRes(SecurityConfig.LOGIN_SUCC_URL); loginRes.setUserId(authUser.getOprtrId()); loginRes.setUserNm(authUser.getOprtrNm()); - + loginRes.setAuthToken(authToken); + loginRes.setMenuUrls(menuUrls); + ChkAuthNumResDto result = new ChkAuthNumResDto(rCode, loginRes); return result; }else { @@ -255,8 +289,24 @@ public class LoginController { if (auth != null){ new SecurityContextLogoutHandler().logout(request, response, auth); } - jwtSvc.destroyPrivateToken(request, response); +// jwtSvc.destroyPrivateToken(request, response); return new LogoutResDto(); } + + /** + * date : 2022. 8. 17. + * auth : ckr + * desc : 2차 인증 페이지 진입시 accessToken 체크 + * @param loginCheckDto + * @param request + * @param response + * @return LoginResDto + */ + @PostMapping("loginCheck") + @ResponseBody + public LoginResDto loginCheck(@RequestBody LoginReqDto loginCheckDto, HttpServletRequest request, HttpServletResponse response) { + + return loginService.loginCheck(loginCheckDto); + } } diff --git a/src/main/java/kr/co/uplus/ez/api/login/LoginService.java b/src/main/java/kr/co/uplus/ez/api/login/LoginService.java index 7865bdf..6e533e4 100644 --- a/src/main/java/kr/co/uplus/ez/api/login/LoginService.java +++ b/src/main/java/kr/co/uplus/ez/api/login/LoginService.java @@ -1,12 +1,9 @@ package kr.co.uplus.ez.api.login; -import kr.co.uplus.ez.api.login.dto.*; -import kr.co.uplus.ez.common.data.ApiResponseCode; -import kr.co.uplus.ez.common.data.Const; -import kr.co.uplus.ez.common.utils.DateUtils; -import kr.co.uplus.ez.common.utils.EncryptionUtil; -import kr.co.uplus.ez.common.utils.TextUtils; -import kr.co.uplus.ez.config.SecurityConfig; +import java.util.Date; +import java.util.List; +import java.util.Map; + import org.apache.commons.codec.binary.StringUtils; import org.apache.commons.lang3.RandomStringUtils; import org.mybatis.spring.SqlSessionTemplate; @@ -17,8 +14,27 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import java.util.Date; -import java.util.List; +import io.jsonwebtoken.Claims; +import kr.co.uplus.ez.api.login.dto.AuthNum; +import kr.co.uplus.ez.api.login.dto.AuthNumReqDto; +import kr.co.uplus.ez.api.login.dto.AuthNumResDto; +import kr.co.uplus.ez.api.login.dto.AuthUser; +import kr.co.uplus.ez.api.login.dto.ChkAuthNumReqDto; +import kr.co.uplus.ez.api.login.dto.LoginReqDto; +import kr.co.uplus.ez.api.login.dto.LoginRes; +import kr.co.uplus.ez.api.login.dto.LoginResDto; +import kr.co.uplus.ez.api.login.dto.ResetPasswordReqDto; +import kr.co.uplus.ez.api.login.dto.ResetPasswordResDto; +import kr.co.uplus.ez.api.login.dto.SendMsgDto; +import kr.co.uplus.ez.api.login.dto.UpdatePasswordReqDto; +import kr.co.uplus.ez.api.login.dto.UpdatePasswordResDto; +import kr.co.uplus.ez.common.data.ApiResponseCode; +import kr.co.uplus.ez.common.data.Const; +import kr.co.uplus.ez.common.jwt.JwtService; +import kr.co.uplus.ez.common.utils.DateUtils; +import kr.co.uplus.ez.common.utils.EncryptionUtil; +import kr.co.uplus.ez.common.utils.TextUtils; +import kr.co.uplus.ez.config.SecurityConfig; @Service public class LoginService { @@ -49,6 +65,9 @@ public class LoginService { @Value("${authentication.cntLimit: 10}") private int cntLimit; + @Autowired + private JwtService jwtSvc; + /** * 1차 로그인 인증 * @@ -105,7 +124,13 @@ public class LoginService { return new LoginResDto(ApiResponseCode.LOGIN_UPDATE_PWD, new LoginRes(SecurityConfig.LOGIN_UPDATE_PWD_URL)); } - return new LoginResDto(ApiResponseCode.SUCCESS, new LoginRes(SecurityConfig.LOGIN_SUCC_URL)); + // access 토큰 생성 + LoginRes loginRes = new LoginRes(); + + String accessToken = jwtSvc.accessToken(user); + loginRes.setAccessToken(accessToken); + loginRes.setNextUrl(SecurityConfig.VUE_URL_ARRY[0].toString()); + return new LoginResDto(ApiResponseCode.SUCCESS, loginRes); } /** @@ -433,5 +458,74 @@ public class LoginService { dbUserParam.setOprtrId(userId); return authUserMapper.getUser(dbUserParam); } + + /** + * access 토큰 확인 + * + * @param loginCheckDto + * @return LoginResDto + */ + @SuppressWarnings("unchecked") + public LoginResDto loginCheck(LoginReqDto loginCheckDto){ + String accessTokenStr = ""; + try { + accessTokenStr = loginCheckDto.getAccessToken(); + // validate check + if(accessTokenStr == null || accessTokenStr.equals("")) { + // 에러 코드 처리 + return new LoginResDto(ApiResponseCode.CE_AUTH_ERROR); + } + String oprtrId = loginCheckDto.getOprtrId(); + if(oprtrId == null || oprtrId.equals("")) { + // 에러 코드 처리 + return new LoginResDto(ApiResponseCode.CE_AUTH_ERROR); + } + + Claims claims = jwtSvc.parseToken(accessTokenStr); + String tokenId = claims.get("jti").toString(); + + if(!oprtrId.equals(tokenId)) { + // 에러 코드 처리 + return new LoginResDto(ApiResponseCode.CE_AUTH_ERROR); + } + + // 토큰 파싱 + Map principal = (Map) claims.get("principal"); + String parsingOprtrId = principal.get("oprtrId").toString(); + String parsingOprtrPwd = principal.get("pwd").toString(); + + if(!parsingOprtrId.equals(tokenId)) { + // 에러 코드 처리 + return new LoginResDto(ApiResponseCode.CE_AUTH_ERROR); + } + + // get user info + LoginMapper loginMapper = sqlSessionMaster.getMapper(LoginMapper.class); + String userId = parsingOprtrId; + AuthUser dbUserParam = new AuthUser(); + dbUserParam.setOprtrId(userId); + AuthUser user = loginMapper.getUser(dbUserParam); + + if(user == null) { + // 사용자 조회 결과 없음 + return new LoginResDto(ApiResponseCode.CE_AUTH_ERROR); + } + + if(!parsingOprtrPwd.equals(user.getPwd())) { + // 에러 코드 처리 + return new LoginResDto(ApiResponseCode.CE_AUTH_ERROR); + } + + + }catch(Exception e) { + logger.info(e.getMessage()); + return null; + } + + LoginRes loginRes = new LoginRes(); + loginRes.setAccessToken(accessTokenStr); + + return new LoginResDto(ApiResponseCode.SUCCESS, loginRes); + } } diff --git a/src/main/java/kr/co/uplus/ez/api/login/dto/LoginReqDto.java b/src/main/java/kr/co/uplus/ez/api/login/dto/LoginReqDto.java index b9fc67d..e64a894 100644 --- a/src/main/java/kr/co/uplus/ez/api/login/dto/LoginReqDto.java +++ b/src/main/java/kr/co/uplus/ez/api/login/dto/LoginReqDto.java @@ -13,4 +13,7 @@ public class LoginReqDto implements Serializable { private String oprtrId; @ApiModelProperty(example = "어드민 사용자 Pw", name = "어드민 사용자 Pw", dataType = "String") private String oprtrPw; + + @ApiModelProperty(hidden = true) + private String accessToken; } \ No newline at end of file diff --git a/src/main/java/kr/co/uplus/ez/api/login/dto/LoginRes.java b/src/main/java/kr/co/uplus/ez/api/login/dto/LoginRes.java index 82228e2..080ebb5 100644 --- a/src/main/java/kr/co/uplus/ez/api/login/dto/LoginRes.java +++ b/src/main/java/kr/co/uplus/ez/api/login/dto/LoginRes.java @@ -1,10 +1,11 @@ package kr.co.uplus.ez.api.login.dto; +import java.io.Serializable; +import java.util.ArrayList; + import io.swagger.annotations.ApiModelProperty; import lombok.Data; -import java.io.Serializable; - @SuppressWarnings("serial") @Data public class LoginRes implements Serializable { @@ -18,7 +19,13 @@ public class LoginRes implements Serializable { @ApiModelProperty(name = "사용자명", example = "사용자명", dataType = "String") private String userNm; - + /** 접근 토큰*/ + private String accessToken; + /** 권한 토큰*/ + private String authToken; + /** 메뉴 url*/ + private ArrayList menuUrls; + public LoginRes(String nextUrl) { super(); this.nextUrl = nextUrl; diff --git a/src/main/java/kr/co/uplus/ez/common/data/ApiResponseCode.java b/src/main/java/kr/co/uplus/ez/common/data/ApiResponseCode.java index 95e550a..191a9f9 100644 --- a/src/main/java/kr/co/uplus/ez/common/data/ApiResponseCode.java +++ b/src/main/java/kr/co/uplus/ez/common/data/ApiResponseCode.java @@ -65,6 +65,9 @@ public enum ApiResponseCode { ,CE_SENDMGT_DUPLICATE_SENDNUM("4021", "이미 등록된 발신번호로 저장 불가.") /** 인증번호 발송 요청 임계값 초과 분당 10회 */ ,CE_AUTHNUM_STAT_LOCK("4022", "인증요청 제한") + /** 토큰 인증 에러*/ + ,CE_AUTH_ERROR("4023", "토큰 인증 에러") + // 시스템 /** 알 수 없는 에러. */ ,SE_UNKNOWN("9999", "알 수 없는 에러"); diff --git a/src/main/java/kr/co/uplus/ez/common/jwt/JwtAuthCookieFilter.java b/src/main/java/kr/co/uplus/ez/common/jwt/JwtAuthCookieFilter.java index 5ed73d5..bb7d165 100644 --- a/src/main/java/kr/co/uplus/ez/common/jwt/JwtAuthCookieFilter.java +++ b/src/main/java/kr/co/uplus/ez/common/jwt/JwtAuthCookieFilter.java @@ -1,58 +1,67 @@ -package kr.co.uplus.ez.common.jwt; - -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.beans.factory.annotation.Autowired; - -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.ExpiredJwtException; -import io.jsonwebtoken.JwtException; - -public class JwtAuthCookieFilter extends JwtAuthFilter { - @Autowired - private JwtService jwtSvc; - - - public JwtAuthCookieFilter(JwtProperties jwtProps) { - super(jwtProps); - } - - @Override - public String getToken(HttpServletRequest request) { - String payload = null, signature = null; - Cookie[] cookies = request.getCookies(); - if (cookies != null) { - for (Cookie cookie : cookies) { - // 1. 쿠키에서 jwt 토큰 header.paload 부분 읽기 - if (jwtProps.getPart1().equals(cookie.getName())) { - payload = cookie.getValue(); - } - // 2. 쿠키에서 jwt 토큰 signature 부분 읽기 - else if (jwtProps.getPart2().equals(cookie.getName())) { - signature = cookie.getValue(); - } - } - } - if (cookies == null || payload == null || signature == null) { - return null; - } - String token = payload + "." + signature; - return token; - } - - @Override - public void onValidateSuccess(HttpServletRequest request, HttpServletResponse response, Claims claims) { - // 토큰 업데이트 - Sliding Sessions - jwtSvc.updatePrivateToken(response, claims); - } - - @Override - public void onValidateException(HttpServletRequest request, HttpServletResponse response, JwtException exception) { - if (exception instanceof ExpiredJwtException) { - jwtSvc.destroyPrivateToken(request, response); - } - } - -} \ No newline at end of file +//package kr.co.uplus.ez.common.jwt; +// +//import javax.servlet.http.Cookie; +//import javax.servlet.http.HttpServletRequest; +//import javax.servlet.http.HttpServletResponse; +// +//import org.springframework.beans.factory.annotation.Autowired; +// +//import io.jsonwebtoken.Claims; +//import io.jsonwebtoken.ExpiredJwtException; +//import io.jsonwebtoken.JwtException; +// +//public class JwtAuthCookieFilter extends JwtAuthFilter { +// @Autowired +// private JwtService jwtSvc; +// +// +// public JwtAuthCookieFilter(JwtProperties jwtProps) { +// super(jwtProps); +// } +// +// @Override +// public String getToken(HttpServletRequest request) { +//// String payload = null, signature = null; +//// Cookie[] cookies = request.getCookies(); +//// if (cookies != null) { +//// for (Cookie cookie : cookies) { +//// // 1. 쿠키에서 jwt 토큰 header.paload 부분 읽기 +//// if (jwtProps.getPart1().equals(cookie.getName())) { +//// payload = cookie.getValue(); +//// } +//// // 2. 쿠키에서 jwt 토큰 signature 부분 읽기 +//// else if (jwtProps.getPart2().equals(cookie.getName())) { +//// signature = cookie.getValue(); +//// } +//// } +//// } +//// if (cookies == null || payload == null || signature == null) { +//// return null; +//// } +//// String token = payload + "." + signature; +//// return token; +// // 1. get jwtToken +// String header = request.getHeader(jwtProps.getHeader().toLowerCase()); +// // 2. 헤더 값 검사 +// if(header == null || !header.startsWith(jwtProps.getPrefix())) { +// return null; +// } +// +// // 3. Authorization 헤더에서 토큰 추출 +// return header.replace(jwtProps.getPrefix(), ""); +// } +// +// @Override +// public void onValidateSuccess(HttpServletRequest request, HttpServletResponse response, Claims claims) { +// // 토큰 업데이트 - Sliding Sessions +// jwtSvc.updatePrivateToken(response, claims); +// } +// +// @Override +// public void onValidateException(HttpServletRequest request, HttpServletResponse response, JwtException exception) { +// if (exception instanceof ExpiredJwtException) { +// jwtSvc.destroyPrivateToken(request, response); +// } +// } +// +//} \ No newline at end of file diff --git a/src/main/java/kr/co/uplus/ez/common/jwt/JwtAuthHeaderFilter.java b/src/main/java/kr/co/uplus/ez/common/jwt/JwtAuthHeaderFilter.java index 1655449..7c122fe 100644 --- a/src/main/java/kr/co/uplus/ez/common/jwt/JwtAuthHeaderFilter.java +++ b/src/main/java/kr/co/uplus/ez/common/jwt/JwtAuthHeaderFilter.java @@ -1,13 +1,23 @@ package kr.co.uplus.ez.common.jwt; +import java.util.Enumeration; +import java.util.HashMap; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; + import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.JwtException; +import lombok.extern.slf4j.Slf4j; +@Slf4j public class JwtAuthHeaderFilter extends JwtAuthFilter { - + @Autowired + private JwtService jwtSvc; + public JwtAuthHeaderFilter(JwtProperties jwtProps) { super(jwtProps); } @@ -15,9 +25,22 @@ public class JwtAuthHeaderFilter extends JwtAuthFilter { @Override public String getToken(HttpServletRequest request) { + log.info("uri : {}", request.getRequestURI().toString()); + // 1. 공통 헤더 체크 + HashMap headerMap = new HashMap(); + Enumeration enumeration = request.getHeaderNames(); + if (enumeration != null) { + while (enumeration.hasMoreElements()) { + String name = enumeration.nextElement(); + headerMap.put(name.toLowerCase(), request.getHeader(name)); + } + } + log.info("headerMap : {}",headerMap.toString()); + // 1. access token이 저장된 헤더 읽기 - String header = request.getHeader(jwtProps.getHeader()); - + String header = request.getHeader(jwtProps.getHeader().toLowerCase()); + + log.info("header authToken: {}" , header); // 2. 헤더 값 검사 if(header == null || !header.startsWith(jwtProps.getPrefix())) { return null; @@ -29,10 +52,16 @@ public class JwtAuthHeaderFilter extends JwtAuthFilter { @Override public void onValidateSuccess(HttpServletRequest request, HttpServletResponse response, Claims claims) { +// jwtSvc.updatePrivateToken(response, claims); + } @Override public void onValidateException(HttpServletRequest request, HttpServletResponse response, JwtException exception) { + if (exception instanceof ExpiredJwtException) { + //jwtSvc.destroyPrivateToken(request, response); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } } } \ No newline at end of file diff --git a/src/main/java/kr/co/uplus/ez/common/jwt/JwtService.java b/src/main/java/kr/co/uplus/ez/common/jwt/JwtService.java index 90196f3..335ce73 100644 --- a/src/main/java/kr/co/uplus/ez/common/jwt/JwtService.java +++ b/src/main/java/kr/co/uplus/ez/common/jwt/JwtService.java @@ -35,7 +35,7 @@ public class JwtService { @Qualifier("sqlSessionTemplateDb2") private SqlSessionTemplate sqlSessionSlave; - public void generatePrivateToken(HttpServletResponse response, Authentication auth) { + public String generatePrivateToken(HttpServletResponse response, Authentication auth) { Claims claims = coreClaims(auth, jwtProps.getPrivateTokenExpiration()); // 필요하면 다른 정보 추가 @@ -45,7 +45,8 @@ public class JwtService { String token = generateToken(claims); // 쿠키에 토큰 추가 - 보안 강화 - setTokenToCookie(response, token); + //setTokenToCookie(response, token); + return token; } private Claims coreClaims(Authentication auth, int expire) { @@ -161,13 +162,29 @@ public class JwtService { return generateToken(claims); } + + public String accessToken(AuthUser authUser) { + String setId = authUser.getOprtrId(); + DateTime now = DateTime.now(); + Date expiration = now.plusSeconds(jwtProps.getAccessTokenExpiration()).toDate(); + + Claims claims = Jwts.claims() + .setId(setId) + .setIssuedAt(now.toDate()) + .setExpiration(expiration); + + JwtUser jwtUser = JwtUser.createJwtUser(authUser); + claims.put("principal", jwtUser); + + return generateToken(claims); + } private String refreshToken(Authentication auth) { Claims claims = coreClaims(auth, jwtProps.getRefreshTokenExpiration()); return generateToken(claims); } - private Claims parseToken(String token) { + public Claims parseToken(String token) { try { Claims claims = Jwts.parser() .setSigningKey(TextCodec.BASE64.decode(jwtProps.getKeyString())) diff --git a/src/main/java/kr/co/uplus/ez/common/jwt/JwtUser.java b/src/main/java/kr/co/uplus/ez/common/jwt/JwtUser.java index 4fa3334..2398bab 100644 --- a/src/main/java/kr/co/uplus/ez/common/jwt/JwtUser.java +++ b/src/main/java/kr/co/uplus/ez/common/jwt/JwtUser.java @@ -24,6 +24,7 @@ public class JwtUser extends AuthUser { u.setOprtrNm(user.getUsername()); u.setHpNo(user.getHpNo()); u.setAuthorities(user.getAuthorities()); + u.setPwd(user.getPassword()); return u; } diff --git a/src/main/java/kr/co/uplus/ez/config/SecurityConfig.java b/src/main/java/kr/co/uplus/ez/config/SecurityConfig.java index 4aed995..2cc7658 100644 --- a/src/main/java/kr/co/uplus/ez/config/SecurityConfig.java +++ b/src/main/java/kr/co/uplus/ez/config/SecurityConfig.java @@ -20,7 +20,7 @@ import org.springframework.web.cors.CorsUtils; import kr.co.uplus.ez.api.login.LoginFailureHandler; import kr.co.uplus.ez.api.login.LoginSuccessHandler; -import kr.co.uplus.ez.common.jwt.JwtAuthCookieFilter; +//import kr.co.uplus.ez.common.jwt.JwtAuthCookieFilter; import kr.co.uplus.ez.common.jwt.JwtAuthHeaderFilter; import kr.co.uplus.ez.common.jwt.JwtExceptionFilter; import kr.co.uplus.ez.common.jwt.JwtProperties; @@ -31,7 +31,7 @@ import kr.co.uplus.ez.config.filter.VueStaticFilter; public class SecurityConfig extends WebSecurityConfigurerAdapter { private static final String LOGIN_FORM_URL = "/login"; - public static final String LOGIN_API_URL = "/api/v1/bo/login"; + public static final String LOGIN_API_URL = "/api/v1/bo/login/**"; public static final String LOGIN_FAIL_URL = "/login?error=true"; public static final String LOGIN_SUCC_URL = "/"; public static final String LOGIN_UPDATE_PWD_URL = "/view/login/updatePassword"; @@ -40,6 +40,15 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { public static final String PUBLIC_API_URL = "/api/v1/bo/**"; // 내부에서 인증없이 호출하는 API public static final String[] REST_API_URLS = {API_URL, TEST_PERMIT_URL}; + public static final String[] VUE_URL_ARRY = { + "/view/login/auth" + ,"/custMgt/subsList" + ,"/custMgt/memberList" + ,"/custMgt/subsDetail" + ,"/custMgt/memberDetail" + ,"/custMgt/memberAdminDetail" + }; + private static final String[] PERMIT_URL_ARRAY = { "/login", "/api/v1/bo/login/**", @@ -47,7 +56,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { "/swagger-resources/**", "/v3/api-docs", "/v3/api-docs/**", - "/" + "/", + "/socket/**" }; private static final String[] AUTH_URL_ARRAY = { @@ -59,7 +69,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { "/api/v1/bo/sendNumMgt/**", "/api/v1/bo/mntrng/**", "/api/v1/bo/riskMgt/sendNum/**", - "/api/v1/bo/stats/**" + "/api/v1/bo/stats/**", + "/view/error/**" }; @@ -78,17 +89,17 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { .antMatchers("/static/**", "/assets/**"); } - @Bean - public Filter jwtAuthFilter() { - return new JwtAuthCookieFilter(jwtProps); - } +// @Bean +// public Filter jwtAuthFilter() { +// return new JwtAuthCookieFilter(jwtProps); +// } @Override public void configure(HttpSecurity http) throws Exception { http .addFilterBefore(new VueStaticFilter(), UsernamePasswordAuthenticationFilter.class) // Vue에서 호출시 화면관련 URL은 / forward .addFilterBefore(new JwtExceptionFilter(), UsernamePasswordAuthenticationFilter.class) - .addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class) +// .addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class) .addFilterBefore(new JwtAuthHeaderFilter(jwtProps), UsernamePasswordAuthenticationFilter.class); http