From 6817ddecce952266559d2b695c3ba8358be92a8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=90=ED=99=8D=EC=84=9D?= <78216059+bayy1216@users.noreply.github.com> Date: Sun, 8 Sep 2024 23:44:02 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[Fix]:=20=EC=A3=BC=EC=84=9D,=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/haedal/zzansuni/auth/controller/AuthController.java | 5 +---- .../java/org/haedal/zzansuni/auth/domain/AuthService.java | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/auth/controller/AuthController.java b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/auth/controller/AuthController.java index 011c478..b779130 100644 --- a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/auth/controller/AuthController.java +++ b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/auth/controller/AuthController.java @@ -9,7 +9,6 @@ import org.haedal.zzansuni.user.domain.UserModel; import org.haedal.zzansuni.global.jwt.JwtToken; import org.springframework.data.util.Pair; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; @@ -19,9 +18,7 @@ @RequiredArgsConstructor @RestController public class AuthController { - private final AuthService authService; - private final BCryptPasswordEncoder passwordEncoder; @Operation(summary = "oauth2 로그인", description = "oauth2 code를 이용하여 로그인한다.") @PostMapping("/api/auth/oauth2") @@ -51,7 +48,7 @@ public ApiResponse login( return ApiResponse.success(response); } - @Operation(summary = "액세스 토큰 재발급", description = "리프레시 토큰을 이용하여 액세스 토큰을 재발급한다.") + @Operation(summary = "JWT 재발급", description = "리프레시 토큰을 이용하여 JWT를 재발급한다. 발급에 사용된 리프레시 토큰은 만료된다.") @PostMapping("/api/auth/refresh") public ApiResponse refresh( @RequestHeader("Authorization") String authorization diff --git a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/auth/domain/AuthService.java b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/auth/domain/AuthService.java index 43d6cc6..6aefc08 100644 --- a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/auth/domain/AuthService.java +++ b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/auth/domain/AuthService.java @@ -113,7 +113,7 @@ public JwtToken reissueToken(String rawToken) { } /** - * 중복 uuid 저장이 발생하는 경우를 대비하여 10번까지 시도한다. + * 중복 uuid 저장이 발생하는 경우를 대비하여 5번까지 시도한다. * 유저 생성과 리프래시토큰 저장을 한 트랜잭션에서 처리하게 된다면 * 모든 처리가 롤백되어야 한다.
* 데이터베이스에서 발생한 에러는 트랜잭션 상태에 영향을 미쳐 예외가 발생한 후의 추가 처리에 실패한다. From acc12348e68014f01d4e12c9e54cc17a858fac07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=90=ED=99=8D=EC=84=9D?= <78216059+bayy1216@users.noreply.github.com> Date: Sun, 8 Sep 2024 23:45:34 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[Refactor]:=20JWT=20=EB=B0=9C=EA=B8=89,=20?= =?UTF-8?q?=EC=9C=A0=ED=9A=A8=EC=84=B1=EA=B2=80=EC=82=AC=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=EB=88=84=EB=9D=BD=20=EC=88=98=EC=A0=95,=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/domain/CreateJwtUseCase.java | 5 ++- .../haedal/zzansuni/global/jwt/JwtToken.java | 4 +-- .../haedal/zzansuni/global/jwt/JwtUtils.java | 35 ++++++++++++------- .../zzansuni/global/security/JwtProvider.java | 2 +- 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/auth/domain/CreateJwtUseCase.java b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/auth/domain/CreateJwtUseCase.java index d19b578..6e2a346 100644 --- a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/auth/domain/CreateJwtUseCase.java +++ b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/auth/domain/CreateJwtUseCase.java @@ -12,6 +12,7 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; +import java.time.ZoneId; @Slf4j @Component @@ -32,7 +33,9 @@ public JwtToken invoke(User user) { JwtUser jwtUser = JwtUser.of(user.getId(), user.getRole()); String uuid = uuidHolder.random(); JwtToken jwtToken = jwtUtils.generateToken(jwtUser, uuid); - RefreshToken refreshToken = RefreshToken.create(uuid, user, jwtToken.getRefreshTokenExpireAt()); + + LocalDateTime expiredAt = jwtToken.getRefreshTokenExpireAt().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); + RefreshToken refreshToken = RefreshToken.create(uuid, user, expiredAt); refreshTokenStore.flushSave(refreshToken); return jwtToken; } diff --git a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/global/jwt/JwtToken.java b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/global/jwt/JwtToken.java index 3e6a0c6..e88709a 100644 --- a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/global/jwt/JwtToken.java +++ b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/global/jwt/JwtToken.java @@ -3,14 +3,14 @@ import lombok.Builder; import lombok.Getter; -import java.time.LocalDateTime; +import java.util.Date; @Getter @Builder public class JwtToken { private String accessToken; private String refreshToken; - private LocalDateTime refreshTokenExpireAt; + private Date refreshTokenExpireAt; /** * 유효한 토큰을 나타내는 VO diff --git a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/global/jwt/JwtUtils.java b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/global/jwt/JwtUtils.java index bd64d49..bd8d7a1 100644 --- a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/global/jwt/JwtUtils.java +++ b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/global/jwt/JwtUtils.java @@ -11,7 +11,6 @@ import javax.crypto.spec.SecretKeySpec; import java.security.Key; -import java.time.LocalDateTime; import java.util.Date; @Component @@ -32,9 +31,12 @@ public class JwtUtils implements InitializingBean { public JwtToken generateToken(JwtUser jwtUser, String refreshUuid) { - String accessToken = generateAccessToken(jwtUser); - String refreshToken = generateRefreshToken(jwtUser, refreshUuid); - LocalDateTime refreshTokenExpireAt = LocalDateTime.now().plusSeconds(REFRESH_TOKEN_EXPIRE_TIME/1000); + long systemTimeMillis = System.currentTimeMillis(); + Date accessTokenExpireAt = new Date(systemTimeMillis + ACCESS_TOKEN_EXPIRE_TIME); + Date refreshTokenExpireAt = new Date(systemTimeMillis + REFRESH_TOKEN_EXPIRE_TIME); + + String accessToken = generateAccessToken(jwtUser, accessTokenExpireAt); + String refreshToken = generateRefreshToken(jwtUser, refreshUuid, refreshTokenExpireAt); return JwtToken.builder() .accessToken(accessToken) .refreshToken(refreshToken) @@ -46,10 +48,20 @@ public JwtToken generateToken(JwtUser jwtUser, String refreshUuid) { * Jwt가 유효한지 검사하는 메서드. * 만료시간, 토큰의 유효성을 검사한다. */ - public boolean validateToken(String rawToken) { + public boolean validateAccessToken(String rawToken) { try { Claims claims = extractClaims(rawToken); - return !claims.getExpiration().before(new Date()); + Boolean isAccessToken = claims.get(IS_ACCESS_TOKEN, Boolean.class); + if(!isAccessToken) { + return false; + } + if(!claims.getIssuer().equals(ISSUER)) { + return false; + } + if(claims.getExpiration().before(new Date())) { + return false; + } + return true; } catch (Exception e) {//JwtException, ExpiredJwtException, NullPointerException return false; } @@ -60,7 +72,7 @@ public UserIdAndUuid validateAndGetUserIdAndUuid(String rawToken) { try { Claims claims = extractClaims(rawToken); Boolean isAccessToken = claims.get(IS_ACCESS_TOKEN, Boolean.class); - if (isAccessToken == null || isAccessToken) { + if (isAccessToken) { throw new UnauthorizedException("RefreshToken이 유효하지 않습니다."); } Long userId = Long.parseLong(claims.getSubject()); @@ -88,7 +100,8 @@ public JwtUser getJwtUser(JwtToken.ValidToken token) { private JwtUser claimsToJwtUser(Claims claims) { Role userRole = Role.valueOf(claims.get(ROLE, String.class)); - return JwtUser.of(Long.parseLong(claims.getSubject()), userRole); + Long userId = Long.parseLong(claims.getSubject()); + return JwtUser.of(userId, userRole); } @@ -96,8 +109,7 @@ private JwtUser claimsToJwtUser(Claims claims) { * Jwt 토큰생성 * 엑세스토큰 여부, 유저 id, role을 저장한다. */ - private String generateAccessToken(JwtUser jwtUser) { - Date expireDate = new Date(System.currentTimeMillis() + ACCESS_TOKEN_EXPIRE_TIME); + private String generateAccessToken(JwtUser jwtUser, Date expireDate) { return Jwts.builder() .signWith(secretKey) .claim(ROLE, jwtUser.getRole().toString()) @@ -112,8 +124,7 @@ private String generateAccessToken(JwtUser jwtUser) { * refreshToken 생성 * 엑세스토큰 여부, uuid, 유저 아이디를 저장한다. */ - private String generateRefreshToken(JwtUser jwtUser, String refreshUuid) { - Date expireDate = new Date(System.currentTimeMillis() + REFRESH_TOKEN_EXPIRE_TIME); + private String generateRefreshToken(JwtUser jwtUser, String refreshUuid, Date expireDate) { return Jwts.builder() .signWith(secretKey) .claim(IS_ACCESS_TOKEN, false) diff --git a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/global/security/JwtProvider.java b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/global/security/JwtProvider.java index ad697a6..343033a 100644 --- a/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/global/security/JwtProvider.java +++ b/zzansuni-api-server/app/src/main/java/org/haedal/zzansuni/global/security/JwtProvider.java @@ -31,7 +31,7 @@ public Authentication authenticate(Authentication authentication) throws Authent String token = (String) authentication.getCredentials(); // 토큰을 검증하는 단계 - if (!jwtUtils.validateToken(token)) { + if (!jwtUtils.validateAccessToken(token)) { throw new AuthenticationServiceException("유효하지 않은 토큰입니다."); } JwtUser jwtUser = jwtUtils.getJwtUser(JwtToken.ValidToken.of(token));