From d2f0876cca553c2907ea2b5c42f1b805e23f3d15 Mon Sep 17 00:00:00 2001 From: chaeyoungeee Date: Thu, 18 Jul 2024 00:06:33 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20refresh=20token=20=EB=8F=84=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refresh token을 도입하여 로그인 시 유효 기간이 다른 2개의 토큰이 발급될 수 있도록 하였음 jwt filter도 token type에 따라 검증하여 처리할 수 있도록 변경함 --- .../MZConnent/api/member/LoginController.java | 6 +-- .../jwt/JwtAuthenticationEntryPoint.java | 13 ++++--- .../likelion/MZConnent/jwt/JwtFilter.java | 37 ++++++++++++++++++- .../MZConnent/jwt/token/TokenProvider.java | 11 +++++- .../jwt/token/TokenValidationResult.java | 6 +-- 5 files changed, 58 insertions(+), 15 deletions(-) diff --git a/src/main/java/likelion/MZConnent/api/member/LoginController.java b/src/main/java/likelion/MZConnent/api/member/LoginController.java index 77d3bb2..7c589fb 100644 --- a/src/main/java/likelion/MZConnent/api/member/LoginController.java +++ b/src/main/java/likelion/MZConnent/api/member/LoginController.java @@ -33,10 +33,10 @@ public ResponseEntity register(@Valid @RequestBody CreateMember } Long memberId = loginService.createUser(request); - log.info("계정 생성 성공: {}", memberId); + log.info("회원가입 성공: {}", memberId); return ResponseEntity.ok(new ApiResponseJson( - HttpStatus.OK, null, Map.of("memberId", memberId))); + HttpStatus.OK, "회원가입 성공", Map.of("memberId", memberId))); } @PostMapping("/user/login") @@ -50,7 +50,7 @@ public ResponseEntity login(@Valid @RequestBody LoginMemberRequ log.info("token 발행: {}", tokenResponse.toString()); return ResponseEntity.ok(new ApiResponseJson( - HttpStatus.OK, null, tokenResponse + HttpStatus.OK, "로그인 성공", tokenResponse )); } diff --git a/src/main/java/likelion/MZConnent/jwt/JwtAuthenticationEntryPoint.java b/src/main/java/likelion/MZConnent/jwt/JwtAuthenticationEntryPoint.java index 793e224..d91f443 100644 --- a/src/main/java/likelion/MZConnent/jwt/JwtAuthenticationEntryPoint.java +++ b/src/main/java/likelion/MZConnent/jwt/JwtAuthenticationEntryPoint.java @@ -6,6 +6,7 @@ import jakarta.servlet.http.HttpServletResponse; import likelion.MZConnent.dto.ApiResponseJson; import likelion.MZConnent.jwt.token.TokenInfo; +import likelion.MZConnent.jwt.token.TokenResponse; import likelion.MZConnent.jwt.token.TokenStatus; import likelion.MZConnent.jwt.token.TokenValidationResult; import lombok.extern.slf4j.Slf4j; @@ -34,24 +35,24 @@ public void commence(HttpServletRequest request, HttpServletResponse response, A TokenValidationResult result = (TokenValidationResult) request.getAttribute(VALIDATION_RESULT_KEY); String errorMessage = result.getTokenStatus().getMessage(); - TokenInfo tokenInfo = null; + TokenResponse tokenResponse = null; if (result.getTokenStatus() == TokenStatus.TOKEN_REFRESHED) { - tokenInfo = result.getTokenInfo(); + tokenResponse = result.getTokenResponse(); } - sendError(response, errorMessage, tokenInfo); + sendError(response, errorMessage, tokenResponse); } - private void sendError(HttpServletResponse response, String message, TokenInfo tokenInfo) throws IOException { + private void sendError(HttpServletResponse response, String message, TokenResponse tokenResponse) throws IOException { ApiResponseJson responseJson; response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - if (tokenInfo != null) { - responseJson = new ApiResponseJson(HttpStatus.OK, message, tokenInfo); + if (tokenResponse != null) { + responseJson = new ApiResponseJson(HttpStatus.OK, message, tokenResponse); } else { responseJson = new ApiResponseJson(HttpStatus.valueOf(HttpServletResponse.SC_UNAUTHORIZED), message, null); } diff --git a/src/main/java/likelion/MZConnent/jwt/JwtFilter.java b/src/main/java/likelion/MZConnent/jwt/JwtFilter.java index d37e2ec..3755390 100644 --- a/src/main/java/likelion/MZConnent/jwt/JwtFilter.java +++ b/src/main/java/likelion/MZConnent/jwt/JwtFilter.java @@ -62,10 +62,21 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse handleValidAccessToken(token, tokenValidationResult); filterChain.doFilter(request, response); } - else if (tokenValidationResult.getTokenType().equals(TokenType.REFRESH)){ // TODO: refresh token인 경우 -> refreshtokenlist에 있는지 확인 -> accesstoken 재발급 + else if (tokenValidationResult.getTokenType().equals(TokenType.REFRESH)){ // refresh token인 경우 + // refreshTokenList에 있는지 확인 + if (tokenProvider.isRefreshTokenList(token)) { + //access token 재발급 + handleRefreshToken(request, response, tokenValidationResult, filterChain); + } + else { + // 존재하지 않는 refresh token 처리 + handleInvalidToken(request, response, filterChain); + } + } else { - // TODO: 둘 다 아닌 경우 + // type이 access, refresh 둘 다 아닌 경우 + handleWrongTypeToken(request, response, filterChain); } } @@ -92,6 +103,28 @@ private void handleMissingToken(HttpServletRequest request, HttpServletResponse filterChain.doFilter(request, response); } + + private void handleRefreshToken(HttpServletRequest request, HttpServletResponse response, TokenValidationResult tokenValidationResult, FilterChain filterChain) throws IOException, ServletException { + + TokenResponse newAccessTokenResponse = tokenProvider.recreateAccessToken(tokenValidationResult.getClaims()); + + log.info("access toke 재발행: {}", newAccessTokenResponse.getAccessToken().getToken()); + + request.setAttribute("result", new TokenValidationResult(TokenStatus.TOKEN_REFRESHED, null, null, null, newAccessTokenResponse)); + + filterChain.doFilter(request, response); + } + + private static void handleInvalidToken(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException { + request.setAttribute("result", new TokenValidationResult(TokenStatus.TOKEN_WRONG_SIGNATURE, null, null, null)); + filterChain.doFilter(request, response); + } + + private static void handleWrongTypeToken(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException { + request.setAttribute("result", new TokenValidationResult(TokenStatus.TOKEN_WRONG_TYPE, null, null, null)); + filterChain.doFilter(request, response); + } + // 토큰을 추출하는 함수 private String extractToken(HttpServletRequest request) { String bearerToken = request.getHeader(AUTHORIZATION_HEADER); // Authorization header에서 value를 꺼냄 diff --git a/src/main/java/likelion/MZConnent/jwt/token/TokenProvider.java b/src/main/java/likelion/MZConnent/jwt/token/TokenProvider.java index 33b633e..c51f9ae 100644 --- a/src/main/java/likelion/MZConnent/jwt/token/TokenProvider.java +++ b/src/main/java/likelion/MZConnent/jwt/token/TokenProvider.java @@ -149,7 +149,16 @@ public Authentication getAuthentication(String token, Claims claims) { // blacklist에 존재하는 token인지 확인하는 함수 public boolean isAccessTokenBlackList(String accessToken) { if (accessTokenBlackList.isTokenBlackList(accessToken)) { - log.info("이 access token이 블랙리스트에 존재함"); + log.info("access token이 blacklist에 존재함"); + return true; + } + return false; + } + + // refreshtokenlist에 존재하는 token인지 확인하는 함수 + public boolean isRefreshTokenList(String refreshToken) { + if (refreshTokenList.isRefreshTokenList(refreshToken)) { + log.info("존재하는 refresh token임"); return true; } return false; diff --git a/src/main/java/likelion/MZConnent/jwt/token/TokenValidationResult.java b/src/main/java/likelion/MZConnent/jwt/token/TokenValidationResult.java index 9fc2389..ef70d93 100644 --- a/src/main/java/likelion/MZConnent/jwt/token/TokenValidationResult.java +++ b/src/main/java/likelion/MZConnent/jwt/token/TokenValidationResult.java @@ -16,7 +16,7 @@ public class TokenValidationResult { private TokenType tokenType; private String tokenId; private Claims claims; - private TokenInfo tokenInfo = null; + private TokenResponse tokenResponse = null; public TokenValidationResult(TokenStatus tokenStatus, TokenType tokenType, String tokenId, Claims claims) { this.tokenStatus = tokenStatus; @@ -25,12 +25,12 @@ public TokenValidationResult(TokenStatus tokenStatus, TokenType tokenType, Strin this.claims = claims; } - public TokenValidationResult(TokenStatus tokenStatus, TokenType tokenType, String tokenId, Claims claims, TokenInfo tokenInfo) { + public TokenValidationResult(TokenStatus tokenStatus, TokenType tokenType, String tokenId, Claims claims, TokenResponse tokenResponse) { this.tokenStatus = tokenStatus; this.tokenType = tokenType; this.tokenId = tokenId; this.claims = claims; - this.tokenInfo = tokenInfo; + this.tokenResponse = tokenResponse; } public String getEmail() {