From 35e95b81d8a33bf82691858bafd41b96a68c6721 Mon Sep 17 00:00:00 2001 From: Ahn Jiwan Date: Wed, 24 Jan 2024 18:55:18 +0900 Subject: [PATCH] =?UTF-8?q?:sparkles:=20feat:=20refreshToken=20=EC=9E=AC?= =?UTF-8?q?=EB=B0=9C=EA=B8=89=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?(#144)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../diareat/auth/component/JwtAuthFilter.java | 2 +- .../auth/component/JwtTokenProvider.java | 11 +++++- .../auth/controller/AuthController.java | 36 +++++++++++++++---- .../diareat/auth/dto/ResponseJwtDto.java | 10 +++--- .../diareat/util/api/ResponseCode.java | 2 ++ 5 files changed, 47 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/diareat/diareat/auth/component/JwtAuthFilter.java b/src/main/java/com/diareat/diareat/auth/component/JwtAuthFilter.java index e90b21a..e0f61e5 100644 --- a/src/main/java/com/diareat/diareat/auth/component/JwtAuthFilter.java +++ b/src/main/java/com/diareat/diareat/auth/component/JwtAuthFilter.java @@ -23,7 +23,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha String token = jwtTokenProvider.resolveToken((HttpServletRequest) request); // 토큰이 유효하다면 - if (token != null && jwtTokenProvider.validateToken(token)) { + if (token != null && jwtTokenProvider.validateAccessToken(token)) { // 토큰으로부터 유저 정보를 받아 Authentication authentication = jwtTokenProvider.getAuthentication(token); // SecurityContext 에 객체 저장 diff --git a/src/main/java/com/diareat/diareat/auth/component/JwtTokenProvider.java b/src/main/java/com/diareat/diareat/auth/component/JwtTokenProvider.java index d64c936..eaf4ddf 100644 --- a/src/main/java/com/diareat/diareat/auth/component/JwtTokenProvider.java +++ b/src/main/java/com/diareat/diareat/auth/component/JwtTokenProvider.java @@ -1,5 +1,7 @@ package com.diareat.diareat.auth.component; +import com.diareat.diareat.util.api.ResponseCode; +import com.diareat.diareat.util.exception.BaseException; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jwts; @@ -80,7 +82,7 @@ public Long getUserPk(String token) { } // 토큰 유효성, 만료일자 확인 - public boolean validateToken(String jwtToken) { + public boolean validateAccessToken(String jwtToken) { try { Jws claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwtToken); return !claims.getBody().getExpiration().before(new Date()); @@ -89,6 +91,13 @@ public boolean validateToken(String jwtToken) { } } + public void validateRefreshToken(Long userPK, String refreshToken) { + String redisRefreshToken = redisTemplate.opsForValue().get(String.valueOf(userPK)); + if (redisRefreshToken == null || !redisRefreshToken.equals(refreshToken)) { + throw new BaseException(ResponseCode.REFRESH_TOKEN_VALIDATION_FAILURE); + } + } + // Request의 Header에서 token 값 가져오기 public String resolveToken(HttpServletRequest request) { return request.getHeader("accessToken"); diff --git a/src/main/java/com/diareat/diareat/auth/controller/AuthController.java b/src/main/java/com/diareat/diareat/auth/controller/AuthController.java index e44ef7f..96fde04 100644 --- a/src/main/java/com/diareat/diareat/auth/controller/AuthController.java +++ b/src/main/java/com/diareat/diareat/auth/controller/AuthController.java @@ -29,8 +29,13 @@ public class AuthController { @PostMapping("/login") public ApiResponse authCheck(@RequestHeader String accessToken) { Long userId = kakaoAuthService.isSignedUp(accessToken); // 유저 고유번호 추출 - String jwt = (userId == null) ? null : jwtTokenProvider.createAccessToken(userId.toString()); // 고유번호가 null이 아니라면 Jwt 토큰 발급 - return ApiResponse.success(ResponseJwtDto.of(userId, jwt), ResponseCode.USER_LOGIN_SUCCESS.getMessage()); + + ResponseJwtDto responseJwtDto = (userId == null) ? null : ResponseJwtDto.builder() + .accessToken(jwtTokenProvider.createAccessToken(userId.toString())) + .refreshToken(jwtTokenProvider.createRefreshToken(userId.toString())) + .build(); + + return ApiResponse.success(responseJwtDto, ResponseCode.USER_LOGIN_SUCCESS.getMessage()); } // 회원가입 (성공 시 Jwt 토큰 발급) @@ -38,14 +43,33 @@ public ApiResponse authCheck(@RequestHeader String accessToken) @PostMapping("/join") public ApiResponse saveUser(@Valid @RequestBody JoinUserDto joinUserDto) { Long userId = userService.saveUser(kakaoAuthService.createUserDto(joinUserDto)); - String jwt = jwtTokenProvider.createAccessToken(userId.toString()); - return ApiResponse.success(ResponseJwtDto.of(userId, jwt), ResponseCode.USER_CREATE_SUCCESS.getMessage()); + + ResponseJwtDto responseJwtDto = (userId == null) ? null : ResponseJwtDto.builder() + .accessToken(jwtTokenProvider.createAccessToken(userId.toString())) + .refreshToken(jwtTokenProvider.createRefreshToken(userId.toString())) + .build(); + + return ApiResponse.success(responseJwtDto, ResponseCode.USER_CREATE_SUCCESS.getMessage()); } // 토큰 검증 (Jwt 토큰을 서버에 전송하여, 서버가 유효한 토큰인지 확인하고 True 혹은 예외 반환) @Operation(summary = "[토큰 검증] 토큰 검증", description = "클라이언트가 가지고 있던 Jwt 토큰을 서버에 전송하여, 서버가 유효한 토큰인지 확인하고 OK 혹은 예외를 반환합니다.") @GetMapping("/token") - public ApiResponse tokenCheck(@RequestHeader String jwtToken) { - return ApiResponse.success(jwtTokenProvider.validateToken(jwtToken), ResponseCode.TOKEN_CHECK_SUCCESS.getMessage()); + public ApiResponse tokenCheck(@RequestHeader String accessToken) { + return ApiResponse.success(jwtTokenProvider.validateAccessToken(accessToken), ResponseCode.TOKEN_CHECK_SUCCESS.getMessage()); + } + + @Operation(summary = "[토큰 재발급] 토큰 재발급", description = "클라이언트가 가지고 있던 Refresh 토큰을 서버에 전송하여, 서버가 유효한 토큰인지 확인하고 OK 혹은 예외를 반환합니다.") + @PostMapping("/reissue") + public ApiResponse reissueToken(@RequestHeader String refreshToken) { + Long userId = jwtTokenProvider.getUserPk(refreshToken); + jwtTokenProvider.validateRefreshToken(userId, refreshToken); + + ResponseJwtDto responseJwtDto = (userId == null) ? null : ResponseJwtDto.builder() + .accessToken(jwtTokenProvider.createAccessToken(userId.toString())) + .refreshToken(jwtTokenProvider.createRefreshToken(userId.toString())) + .build(); + + return ApiResponse.success(responseJwtDto, ResponseCode.TOKEN_REISSUE_SUCCESS.getMessage()); } } diff --git a/src/main/java/com/diareat/diareat/auth/dto/ResponseJwtDto.java b/src/main/java/com/diareat/diareat/auth/dto/ResponseJwtDto.java index b26e30e..41df37b 100644 --- a/src/main/java/com/diareat/diareat/auth/dto/ResponseJwtDto.java +++ b/src/main/java/com/diareat/diareat/auth/dto/ResponseJwtDto.java @@ -1,16 +1,14 @@ package com.diareat.diareat.auth.dto; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; @Getter +@Builder @AllArgsConstructor public class ResponseJwtDto { - private Long id; - private String jwt; - - public static ResponseJwtDto of(Long id, String jwt) { - return new ResponseJwtDto(id, jwt); - } + private String accessToken; + private String refreshToken; } diff --git a/src/main/java/com/diareat/diareat/util/api/ResponseCode.java b/src/main/java/com/diareat/diareat/util/api/ResponseCode.java index ae34056..5943e1d 100644 --- a/src/main/java/com/diareat/diareat/util/api/ResponseCode.java +++ b/src/main/java/com/diareat/diareat/util/api/ResponseCode.java @@ -14,6 +14,7 @@ public enum ResponseCode { // 401 Unauthorized TOKEN_VALIDATION_FAILURE(HttpStatus.UNAUTHORIZED, false, "토큰 검증 실패"), + REFRESH_TOKEN_VALIDATION_FAILURE(HttpStatus.UNAUTHORIZED, false, "Refresh 토큰 검증 실패"), // 403 Forbidden FORBIDDEN(HttpStatus.FORBIDDEN, false, "권한이 없습니다."), @@ -56,6 +57,7 @@ public enum ResponseCode { FOOD_RANK_READ_SUCCESS(HttpStatus.OK, true, "식습관 점수 기반 랭킹 조회 성공"), TOKEN_CHECK_SUCCESS(HttpStatus.OK, true, "토큰 검증 완료"), + TOKEN_REISSUE_SUCCESS(HttpStatus.OK, true, "토큰 재발급 완료"), // 201 Created