-
Notifications
You must be signed in to change notification settings - Fork 0
π Jwt Refresh #36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
π Jwt Refresh #36
Conversation
Walkthroughμ΄ ν 리νμ€νΈλ JWT ν ν° μΈμ¦ μμ€ν μ μ£Όμ 리ν©ν λ§μ ν¬ν¨νκ³ μμ΅λλ€. μ£Όμ λ³κ²½ μ¬νμ λ¨μΌ ν ν°μμ μ‘μΈμ€ λ° λ¦¬νλ μ ν ν° μμ€ν μΌλ‘μ μ ν, JWT μμ± κ΅¬μ± λ³κ²½, μΏ ν€ κ΄λ¦¬ κ°μ λ±μ λλ€. μ΄λ¬ν μμ μ μ ν리μΌμ΄μ μ μΈμ¦ λ©μ»€λμ¦μ λμ± μμ νκ³ μ μ°νκ² λ§λ€κΈ° μν ν¬κ΄μ μΈ μ κ·Ό λ°©μμ λνλ λλ€. Changes
Sequence DiagramsequenceDiagram
participant Client
participant LoginFilter
participant JWTUtil
participant CookieUtil
participant Response
Client->>LoginFilter: λ‘κ·ΈμΈ μμ²
LoginFilter->>JWTUtil: μ‘μΈμ€ ν ν° μμ±
JWTUtil-->>LoginFilter: μ‘μΈμ€ ν ν°
LoginFilter->>JWTUtil: 리νλ μ ν ν° μμ±
JWTUtil-->>LoginFilter: 리νλ μ ν ν°
LoginFilter->>CookieUtil: μ‘μΈμ€ ν ν° μΏ ν€ μμ±
LoginFilter->>CookieUtil: 리νλ μ ν ν° μΏ ν€ μμ±
CookieUtil-->>Response: μΏ ν€ μΆκ°
Response-->>Client: λ‘κ·ΈμΈ μλ΅
Possibly related PRs
Suggested labels
Poem
π Recent review detailsConfiguration used: .coderabbit.yaml π Files selected for processing (1)
π Additional comments (1)
Finishing Touches
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? πͺ§ TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
Documentation and Community
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
π Outside diff range comments (2)
src/main/java/com/mycom/socket/auth/jwt/JWTUtil.java (2)
Line range hint
45-55: ν ν° μμ± λ©μλ 보μ κ°ν νμν ν° μμ± μ ν ν° νμ μ ꡬλΆνκ³ , 보μμ μν μΆκ° claimμ΄ νμν©λλ€.
λ€μκ³Ό κ°μ΄ μμ νλ κ²μ μ μν©λλ€:
- public String createToken(String email, long validityInSeconds) { + private String createToken(String email, String tokenType, long validityInSeconds) { Date now = new Date(); Date validity = new Date(now.getTime() + (validityInSeconds * 1000)); return Jwts.builder() .issuer(jwtProperties.getIssuer()) .subject(email) + .claim("type", tokenType) + .id(UUID.randomUUID().toString()) .issuedAt(now) .expiration(validity) .signWith(secretKey) .compact(); }
Line range hint
60-76: ν ν° κ²μ¦ λ‘μ§ κ°ν νμν ν° κ²μ¦ μ λ§λ£ μκ°κ³Ό ν ν° νμ μ νμΈνλ λ‘μ§μ΄ νμν©λλ€.
λ€μκ³Ό κ°μ΄ μμ νλ κ²μ μ μν©λλ€:
- public boolean validateToken(String token) { + public boolean validateToken(String token, String expectedTokenType) { try { if (!StringUtils.hasText(token)) { return false; } - Jwts.parser() + var claims = Jwts.parser() .verifyWith(secretKey) .requireIssuer(jwtProperties.getIssuer()) .build() - .parseSignedClaims(token); + .parseSignedClaims(token) + .getPayload(); + + String tokenType = claims.get("type", String.class); + if (!expectedTokenType.equals(tokenType)) { + return false; + } + + return !claims.getExpiration().before(new Date()); - return true; } catch (Exception e) { log.warn("JWT ν ν° κ²μ¦ μ€ν¨", e); return false; } }
π§Ή Nitpick comments (9)
src/main/java/com/mycom/socket/global/service/RedisService.java (2)
93-93: null μ²΄ν¬ λ‘μ§μ΄ μ κ°μ λμμ΅λλ€!null μμ μ±μ μν΄ verified κ°μ²΄μ λν λͺ μμ μΈ null 체ν¬λ₯Ό μΆκ°ν κ²μ΄ μ’μ΅λλ€. μ΄λ μ μ¬μ μΈ NullPointerExceptionμ λ°©μ§νκ³ λ μμ μ μΈ μ½λλ₯Ό λ§λλλ€.
νμ§λ§ λͺ κ°μ§ μ μμ¬νμ΄ μμ΅λλ€:
- λ©μλμ JavaDoc μ£Όμμ 보κ°νμ¬ null μ²λ¦¬ λμμ λ¬Έμννλ©΄ μ’μ κ² κ°μ΅λλ€.
- μμλ‘ "true" κ°μ μΆμΆνλ κ²μ κ³ λ €ν΄λ³΄μΈμ.
+ private static final String VERIFICATION_TRUE_VALUE = "true"; public boolean isEmailVerified(String email) { Object verified = redisTemplate.opsForValue().get(VERIFIED_EMAIL_PREFIX + email); - return verified != null && "true".equals(verified.toString()); + return verified != null && VERIFICATION_TRUE_VALUE.equals(verified.toString()); }
Line range hint
16-19: 보μ κ΄λ ¨ ꡬνμ΄ μ λμ΄μμ΅λλ€.λ€μκ³Ό κ°μ 보μ κ΄λ ¨ ꡬνμ¬νλ€μ΄ μ μ μ©λμ΄ μμ΅λλ€:
- ν€ μ λμ¬λ₯Ό ν΅ν λ€μμ€νμ΄μ€ λΆλ¦¬
- μ μ ν TTL μ€μ μΌλ‘ λ―Όκ°ν λ°μ΄ν°μ μλͺ κ΄λ¦¬
- μ΄λ©μΌ μΈμ¦ λ° μλ μ ν λ©μ»€λμ¦
μΆκ° 보μ κ°νλ₯Ό μν μ μ:
- ν€ μ λμ¬λ₯Ό νκ²½ λ³μλ μ€μ νμΌλ‘ λΆλ¦¬νλ κ²μ κ³ λ €ν΄λ³΄μΈμ.
- Redis μ°κ²°μ λν SSL/TLS μ€μ κ²ν κ° νμν©λλ€.
Also applies to: 21-24
src/main/java/com/mycom/socket/auth/service/MailService.java (1)
98-99: μ΄λ©μΌ μΈμ¦ μν μ μ₯μ μμμ± λ³΄μ₯μ΄ νμν©λλ€.μ¬λ¬ μμ²μ΄ λμμ μ²λ¦¬λ λ λ°μν μ μλ λ μ΄μ€ 컨λμ μ λ°©μ§νκΈ° μν΄, Redis μμ μ μμμ±μ 보μ₯νλ κ²μ΄ μ’μ΅λλ€.
RedisServiceμμ λ€μκ³Ό κ°μ μμμ μμ μ ꡬννλ κ²μ κ³ λ €ν΄λ³΄μΈμ:public boolean verifyAndSaveEmailStatus(String email, String code) { return redisTemplate.execute(new SessionCallback<Boolean>() { @Override public Boolean execute(RedisOperations operations) throws DataAccessException { operations.multi(); String savedCode = getCode(email); if (savedCode != null && savedCode.equals(code)) { saveVerifiedEmail(email); operations.exec(); return true; } operations.discard(); return false; } }); }src/main/java/com/mycom/socket/auth/controller/RefreshController.java (3)
31-32:BadRequestExceptionλμUnauthorizedExceptionμ¬μ©μ κΆμ₯ν©λλ€.νμ¬ λ¦¬νλ μ ν ν°μ΄ μ ν¨νμ§ μμ λ
BadRequestExceptionμ λ°μμν€κ³ μμ΅λλ€. μ΄ κ²½μ° μΈμ¦μ΄ νμν μν©μ΄λ―λ‘ λ³΄λ€ μ μ ν HTTP μν μ½λλ₯Ό λ°ννκΈ° μν΄UnauthorizedExceptionμΌλ‘ λ³κ²½νλ κ²μ΄ μ’μ΅λλ€.
45-45: μ‘μΈμ€ ν ν°μ μλ΅ λ³Έλ¬Έκ³Ό μΏ ν€μ μ€λ³΅ μ λ¬νκ³ μμ΅λλ€.μ‘μΈμ€ ν ν°μ μΏ ν€λ‘ μ€μ νλ©΄μ μλ΅ λ³Έλ¬ΈμΌλ‘λ λ°ννκ³ μμ΅λλ€. 보μ λ° μ€λ³΅ μ μ‘μ νΌνκΈ° μν΄ νλμ λ°©λ²μΌλ‘ ν΅μΌνλ κ²μ΄ μ’μ΅λλ€.
49-59: μμΈ λ©μμ§λ₯Ό ꡬ체ννμ¬ μ¬μ©μ κ²½νμ ν₯μμν¬ μ μμ΅λλ€.
extractRefreshTokenλ©μλμμ μΏ ν€κ° μκ±°λ 리νλ μ ν ν° μΏ ν€κ° μμ λ λμΌν μμΈ λ©μμ§λ₯Ό λ°ννκ³ μμ΅λλ€. μ νν μμΈμ νμ νκΈ° μν΄ μμΈ λ©μμ§λ₯Ό ꡬ체μ μΌλ‘ ꡬλΆνλ κ²μ΄ μ’μ΅λλ€.μλ₯Ό λ€μ΄:
- μΏ ν€κ° μμ κ²½μ°: "μΏ ν€κ° μ‘΄μ¬νμ§ μμ΅λλ€. λ‘κ·ΈμΈ μνλ₯Ό νμΈν΄μ£ΌμΈμ."
- 리νλ μ ν ν° μΏ ν€κ° μμ κ²½μ°: "리νλ μ ν ν°μ΄ μμ΅λλ€. λ€μ λ‘κ·ΈμΈν΄μ£ΌμΈμ."
src/main/java/com/mycom/socket/auth/security/LoginFilter.java (1)
54-55:JWTUtilμ ν ν° μμ± λ©μλ μ¬μ© μΌκ΄μ±μ μ μ§νμμμ€.νμ¬
createAccessTokenκ³ΌcreateRefreshTokenλ©μλλ₯Ό μ¬μ©νκ³ μμ΅λλ€. λ€λ₯Έ ν΄λμ€μμλcreateToken(email, validityInSeconds)ννμ λ©μλλ₯Ό μ¬μ©νκ³ μμΌλ―λ‘, λ©μλ μ¬μ©μ μΌκ΄λκ² ν΅μΌνλ κ²μ΄ μ μ§λ³΄μμ λμμ΄ λ©λλ€.src/main/java/com/mycom/socket/auth/service/AuthService.java (1)
51-61: ν ν° μμ± λ° μΏ ν€ μ€μ λ‘μ§μ μ€λ³΅μ κ°μ ν μ μμ΅λλ€.
loginλ©μλμμ ν ν° μμ±κ³Ό μΏ ν€ μ€μ λ‘μ§μ΄LoginFilterν΄λμ€μsuccessfulAuthenticationλ©μλμ μ μ¬νκ² μ€λ³΅λμ΄ μμ΅λλ€. μ΄λ¬ν μ€λ³΅ μ½λλ₯Ό κ³΅ν΅ λ©μλλ‘ μΆμΆνμ¬ μ½λμ μ¬μ¬μ©μ±κ³Ό μ μ§λ³΄μμ±μ λμΌ μ μμ΅λλ€.src/main/java/com/mycom/socket/auth/dto/response/TokenResponse.java (1)
3-8: ν ν° μ ν¨μ± κ²μ¦ μΆκ° νμμ‘μΈμ€ ν ν°μ null λλ λΉ λ¬Έμμ΄ κ²μ¦μ΄ νμν©λλ€. ν©ν 리 λ©μλμμ μ΄λ₯Ό μ²λ¦¬νλ©΄ μ’μ κ² κ°μ΅λλ€.
λ€μκ³Ό κ°μ΄ μμ νλ κ²μ μ μν©λλ€:
public record TokenResponse( String accessToken ) { public static TokenResponse of(String accessToken) { + if (accessToken == null || accessToken.trim().isEmpty()) { + throw new IllegalArgumentException("μ‘μΈμ€ ν ν°μ nullμ΄κ±°λ λΉ κ°μΌ μ μμ΅λλ€."); + } return new TokenResponse(accessToken); } }
π Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
π Files selected for processing (11)
src/main/java/com/mycom/socket/auth/config/JWTProperties.java(1 hunks)src/main/java/com/mycom/socket/auth/controller/RefreshController.java(1 hunks)src/main/java/com/mycom/socket/auth/dto/response/TokenResponse.java(1 hunks)src/main/java/com/mycom/socket/auth/jwt/JWTFilter.java(1 hunks)src/main/java/com/mycom/socket/auth/jwt/JWTUtil.java(1 hunks)src/main/java/com/mycom/socket/auth/security/CookieUtil.java(1 hunks)src/main/java/com/mycom/socket/auth/security/LoginFilter.java(1 hunks)src/main/java/com/mycom/socket/auth/service/AuthService.java(4 hunks)src/main/java/com/mycom/socket/auth/service/MailService.java(1 hunks)src/main/java/com/mycom/socket/global/service/RedisService.java(1 hunks)src/test/java/com/mycom/socket/member/service/LoginTest.java(4 hunks)
π Additional comments (4)
src/main/java/com/mycom/socket/auth/service/MailService.java (1)
95-97: μ½λ λΉκ΅ λ‘μ§μ΄ μ μ ν©λλ€.μΈμ¦ μ½λ λΉκ΅μ μμΈ μ²λ¦¬κ° λͺ ννκ² κ΅¬νλμ΄ μμ΅λλ€.
src/main/java/com/mycom/socket/auth/security/LoginFilter.java (1)
58-61: μΏ ν€ μ€μ μ 보μ μμ±μ νμΈνμμμ€.μΏ ν€λ₯Ό μμ±ν λ
HttpOnly,Secure,SameSiteλ±μ 보μ μ΅μ μ΄ μ μ νκ² μ€μ λμ΄ μλμ§ νμΈνλ κ²μ΄ μ’μ΅λλ€. μ΄λ μΏ ν€ νμ·¨ λ° CSRF 곡격 λ±μ μλ°©νλ λ° λμμ΄ λ©λλ€.src/main/java/com/mycom/socket/auth/service/AuthService.java (1)
115-115: λ‘κ·Έμμ μ 리νλ μ ν ν° μΏ ν€ λ¬΄ν¨ν μ²λ¦¬κ° μ μ ν©λλ€.λ‘κ·Έμμ μ²λ¦¬ μ 리νλ μ ν ν° μΏ ν€λ₯Ό λ§λ£μν€λ λ‘μ§μ΄ μΆκ°λμ΄ μμ΅λλ€. μ΄λ 보μ κ°νλ₯Ό μν΄ νμν μ‘°μΉμ΄λ©°, μ¬λ°λ₯΄κ² ꡬνλμμ΅λλ€.
src/main/java/com/mycom/socket/auth/jwt/JWTFilter.java (1)
49-51: μΏ ν€ μ΄λ¦ λ³κ²½μ΄ μ¬λ°λ₯΄κ² ꡬνλμμ΅λλ€.μ‘μΈμ€ ν ν°μ μν μΏ ν€ μ΄λ¦μ΄ λͺ ννκ² κ΅¬λΆλμ΄ μμ΅λλ€.
리νλ μ ν ν° μ²λ¦¬ λ‘μ§ μΆκ° κ²ν κ° νμν©λλ€.
νμ¬ νν°λ μ‘μΈμ€ ν ν°λ§ μ²λ¦¬νκ³ μμ΅λλ€. 리νλ μ ν ν° κ°±μ λ‘μ§μ μν λ³λμ μλν¬μΈνΈλ νν° κ΅¬νμ κ³ λ €ν΄λ³΄μΈμ.
λ€μ μ€ν¬λ¦½νΈλ‘ 리νλ μ ν ν° κ΄λ ¨ μλν¬μΈνΈ ꡬν μ¬λΆλ₯Ό νμΈν μ μμ΅λλ€:
| String savedCode = redisService.getCode(email); // Redisμμ μ½λλ₯Ό κ°μ Έμ΄ | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redisμμ κ°μ Έμ¨ μ½λμ null 체ν¬κ° νμν©λλ€.
savedCodeκ° nullμΈ κ²½μ° (μ½λκ° λ§λ£λμκ±°λ μ‘΄μ¬νμ§ μλ κ²½μ°) NullPointerExceptionμ΄ λ°μν μ μμ΅λλ€.
λ€μκ³Ό κ°μ΄ null 체ν¬λ₯Ό μΆκ°νλ κ²μ κΆμ₯ν©λλ€:
String savedCode = redisService.getCode(email); // Redisμμ μ½λλ₯Ό κ°μ Έμ΄
+if (savedCode == null) {
+ throw new BaseException("λ§λ£λμκ±°λ μ‘΄μ¬νμ§ μλ μΈμ¦ μ½λμ
λλ€.", HttpStatus.BAD_REQUEST);
+}π Committable suggestion
βΌοΈ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| String savedCode = redisService.getCode(email); // Redisμμ μ½λλ₯Ό κ°μ Έμ΄ | |
| String savedCode = redisService.getCode(email); // Redisμμ μ½λλ₯Ό κ°μ Έμ΄ | |
| if (savedCode == null) { | |
| throw new BaseException("λ§λ£λμκ±°λ μ‘΄μ¬νμ§ μλ μΈμ¦ μ½λμ λλ€.", HttpStatus.BAD_REQUEST); | |
| } |
| private long accessTokenValidityInSeconds; | ||
| private long refreshTokenValidityInSeconds; | ||
| private String accessTokenCookieName; | ||
| private String refreshTokenCookieName; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π οΈ Refactor suggestion
νμ μμ±μ λν μ ν¨μ± κ²μ¦ μΆκ° νμ
ν ν° μ ν¨ κΈ°κ°κ³Ό μΏ ν€ μ΄λ¦μ νμ κ°μ΄λ―λ‘, μ΄μ λν μ ν¨μ± κ²μ¦μ΄ νμν©λλ€.
λ€μκ³Ό κ°μ΄ μμ νλ κ²μ μ μν©λλ€:
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Positive;
@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "jwt")
public class JWTProperties {
private String secret;
- private long accessTokenValidityInSeconds;
- private long refreshTokenValidityInSeconds;
- private String accessTokenCookieName;
- private String refreshTokenCookieName;
+ @Positive(message = "μ‘μΈμ€ ν ν° μ ν¨ κΈ°κ°μ μμμ¬μΌ ν©λλ€")
+ private long accessTokenValidityInSeconds;
+ @Positive(message = "리νλ μ ν ν° μ ν¨ κΈ°κ°μ μμμ¬μΌ ν©λλ€")
+ private long refreshTokenValidityInSeconds;
+ @NotBlank(message = "μ‘μΈμ€ ν ν° μΏ ν€ μ΄λ¦μ νμμ
λλ€")
+ private String accessTokenCookieName;
+ @NotBlank(message = "리νλ μ ν ν° μΏ ν€ μ΄λ¦μ νμμ
λλ€")
+ private String refreshTokenCookieName;
private String issuer = "go_socket";
private boolean secureCookie = false;
}π Committable suggestion
βΌοΈ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private long accessTokenValidityInSeconds; | |
| private long refreshTokenValidityInSeconds; | |
| private String accessTokenCookieName; | |
| private String refreshTokenCookieName; | |
| import jakarta.validation.constraints.NotBlank; | |
| import jakarta.validation.constraints.Positive; | |
| @Getter | |
| @Setter | |
| @Component | |
| @ConfigurationProperties(prefix = "jwt") | |
| public class JWTProperties { | |
| private String secret; | |
| @Positive(message = "μ‘μΈμ€ ν ν° μ ν¨ κΈ°κ°μ μμμ¬μΌ ν©λλ€") | |
| private long accessTokenValidityInSeconds; | |
| @Positive(message = "리νλ μ ν ν° μ ν¨ κΈ°κ°μ μμμ¬μΌ ν©λλ€") | |
| private long refreshTokenValidityInSeconds; | |
| @NotBlank(message = "μ‘μΈμ€ ν ν° μΏ ν€ μ΄λ¦μ νμμ λλ€") | |
| private String accessTokenCookieName; | |
| @NotBlank(message = "리νλ μ ν ν° μΏ ν€ μ΄λ¦μ νμμ λλ€") | |
| private String refreshTokenCookieName; | |
| private String issuer = "go_socket"; | |
| private boolean secureCookie = false; | |
| } |
| Cookie cookie = new Cookie(jwtProperties.getAccessTokenCookieName(), token); | ||
| cookie.setHttpOnly(true); | ||
| cookie.setSecure(jwtProperties.isSecureCookie()); | ||
| cookie.setPath("/"); | ||
| cookie.setMaxAge((int) jwtProperties.getAccessTokenValidityInSeconds()); | ||
| return cookie; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π οΈ Refactor suggestion
μΏ ν€ μμ± λ‘μ§ μ€λ³΅ μ κ±° νμ
μΈμ¦ μΏ ν€μ 리νλ μ μΏ ν€ μμ± λ‘μ§μ΄ μ€λ³΅λμ΄ μμ΅λλ€. κ³΅ν΅ λ©μλλ‘ μΆμΆνλ©΄ μ½λ μ μ§λ³΄μκ° λ μ¬μμ§ κ² κ°μ΅λλ€.
λ€μκ³Ό κ°μ΄ μμ νλ κ²μ μ μν©λλ€:
+ private Cookie createCookie(String name, String value, long maxAgeInSeconds) {
+ Cookie cookie = new Cookie(name, value);
+ cookie.setHttpOnly(true);
+ cookie.setSecure(jwtProperties.isSecureCookie());
+ cookie.setPath("/");
+ cookie.setMaxAge((int) maxAgeInSeconds);
+ return cookie;
+ }
public Cookie createAuthCookie(String token) {
- Cookie cookie = new Cookie(jwtProperties.getAccessTokenCookieName(), token);
- cookie.setHttpOnly(true);
- cookie.setSecure(jwtProperties.isSecureCookie());
- cookie.setPath("/");
- cookie.setMaxAge((int) jwtProperties.getAccessTokenValidityInSeconds());
- return cookie;
+ return createCookie(
+ jwtProperties.getAccessTokenCookieName(),
+ token,
+ jwtProperties.getAccessTokenValidityInSeconds()
+ );
}
public Cookie createRefreshCookie(String token) {
- Cookie cookie = new Cookie(jwtProperties.getRefreshTokenCookieName(), token);
- cookie.setHttpOnly(true);
- cookie.setSecure(jwtProperties.isSecureCookie());
- cookie.setPath("/");
- cookie.setMaxAge((int) jwtProperties.getRefreshTokenValidityInSeconds());
- return cookie;
+ return createCookie(
+ jwtProperties.getRefreshTokenCookieName(),
+ token,
+ jwtProperties.getRefreshTokenValidityInSeconds()
+ );
}Also applies to: 29-34
| Cookie cookie = new Cookie(jwtProperties.getAccessTokenCookieName(), null); | ||
| cookie.setHttpOnly(true); | ||
| cookie.setSecure(true); | ||
| cookie.setPath("/"); | ||
| cookie.setMaxAge(0); // μ¦μ λ§λ£ | ||
| return cookie; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π οΈ Refactor suggestion
λ§λ£λ μΏ ν€ μμ± λ‘μ§ μ€λ³΅ μ κ±° λ° λ³΄μ μ€μ κ°ν νμ
λ§λ£λ μΏ ν€ μμ± λ‘μ§λ μ€λ³΅λμ΄ μμΌλ©°, SameSite μμ± μ€μ μ΄ λλ½λμ΄ μμ΅λλ€.
λ€μκ³Ό κ°μ΄ μμ νλ κ²μ μ μν©λλ€:
+ private Cookie createExpiredCookie(String name) {
+ Cookie cookie = new Cookie(name, null);
+ cookie.setHttpOnly(true);
+ cookie.setSecure(true);
+ cookie.setPath("/");
+ cookie.setMaxAge(0);
+ cookie.setAttribute("SameSite", "Strict");
+ return cookie;
+ }
public Cookie createExpiredAuthCookie() {
- Cookie cookie = new Cookie(jwtProperties.getAccessTokenCookieName(), null);
- cookie.setHttpOnly(true);
- cookie.setSecure(true);
- cookie.setPath("/");
- cookie.setMaxAge(0);
- return cookie;
+ return createExpiredCookie(jwtProperties.getAccessTokenCookieName());
}
public Cookie createExpiredRefreshCookie() {
- Cookie cookie = new Cookie(jwtProperties.getRefreshTokenCookieName(), null);
- cookie.setHttpOnly(true);
- cookie.setSecure(true);
- cookie.setPath("/");
- cookie.setMaxAge(0);
- return cookie;
+ return createExpiredCookie(jwtProperties.getRefreshTokenCookieName());
}Also applies to: 54-59
| public String createAccessToken(String email) { | ||
| return createToken(email, jwtProperties.getAccessTokenValidityInSeconds()); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ν ν° νμ ꡬλΆμ μν claim μΆκ° νμ
μ‘μΈμ€ ν ν°κ³Ό 리νλ μ ν ν°μ ꡬλΆν μ μλ claimμ΄ μμ΄ ν ν° μ νμ νμΈν μ μμ΅λλ€.
λ€μκ³Ό κ°μ΄ μμ νλ κ²μ μ μν©λλ€:
public String createAccessToken(String email) {
- return createToken(email, jwtProperties.getAccessTokenValidityInSeconds());
+ return createToken(email, "ACCESS", jwtProperties.getAccessTokenValidityInSeconds());
}
public String createRefreshToken(String email) {
- return createToken(email, jwtProperties.getRefreshTokenValidityInSeconds());
+ return createToken(email, "REFRESH", jwtProperties.getRefreshTokenValidityInSeconds());
}Also applies to: 38-40
| when(memberRepository.findByEmail(email)).thenReturn(Optional.of(member)); | ||
| when(passwordEncoder.matches(password, encodedPassword)).thenReturn(true); | ||
| when(jwtUtil.createToken(email)).thenReturn(token); | ||
| when(jwtUtil.createToken(email, jwtProperties.getRefreshTokenValidityInSeconds())).thenReturn(token); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π οΈ Refactor suggestion
리νλ μ ν ν° ν μ€νΈ μΌμ΄μ€ 보μμ΄ νμν©λλ€.
νμ¬ ν μ€νΈλ 리νλ μ ν ν° μμ±λ§ κ²μ¦νκ³ μμ΅λλ€. λ€μ μλ리μ€μ λν ν μ€νΈλ₯Ό μΆκ°ν΄μ£ΌμΈμ:
- 리νλ μ ν ν° λ§λ£ μλ리μ€
- 리νλ μ ν ν°μ ν΅ν μ‘μΈμ€ ν ν° κ°±μ
- μλͺ»λ 리νλ μ ν ν° μ²λ¦¬
ν μ€νΈ μΌμ΄μ€ ꡬνμ λμμ΄ νμνμλ€λ©΄ λ§μν΄ μ£ΌμΈμ.
Also applies to: 88-89
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
β»οΈ Duplicate comments (1)
src/main/java/com/mycom/socket/auth/jwt/JWTUtil.java (1)
50-75:β οΈ Potential issueν ν° νμ ꡬλΆμ΄ νμν©λλ€.
μ‘μΈμ€ ν ν°κ³Ό 리νλ μ ν ν°μ ꡬλΆν μ μλ claimμ΄ μμ΄ λ³΄μμ μνν μ μμ΅λλ€.
λ€μκ³Ό κ°μ΄ μμ νλ κ²μ μ μν©λλ€:
- public String createToken(String email, long validityInSeconds) { + public String createToken(String email, String tokenType, long validityInSeconds) { try { return Jwts.builder() .issuer(jwtProperties.getIssuer()) .subject(email) + .claim("type", tokenType) .issuedAt(now) .expiration(validity) .signWith(secretKey) .compact();
π§Ή Nitpick comments (3)
src/main/java/com/mycom/socket/auth/security/CookieUtil.java (1)
55-62: 리νλ μ ν ν° μΏ ν€μ 보μ μ€μ κ°ν κ²ν리νλ μ ν ν°μ μ‘μΈμ€ ν ν°λ³΄λ€ λ λ―Όκ°νλ―λ‘, λ€μκ³Ό κ°μ μΆκ° 보μ μ€μ μ κ³ λ €ν΄λ³΄μΈμ:
- λ μ νμ μΈ μΏ ν€ κ²½λ‘ μ€μ (μ:
/api/auth/refresh)- λ³λμ 보μ λλ©μΈ μ¬μ©
src/main/java/com/mycom/socket/auth/security/LoginFilter.java (1)
Line range hint
1-93: μ 체 μν€ν μ² κ°μ μ μνμ¬ κ΅¬νλ JWT 리νλ μ ν ν° λ©μ»€λμ¦μ λν΄ λ€μκ³Ό κ°μ κ°μ μ¬νμ μ μλ립λλ€:
리νλ μ ν ν° μ μ₯μ ꡬν
- λ°κΈλ 리νλ μ ν ν°μ λ°μ΄ν°λ² μ΄μ€λ Redisμ μ μ₯νμ¬ μΆμ
- ν ν° μ¬μ¬μ© νμ§ λ° μ°¨λ¨ κ°λ₯
ν ν° νμ (Rotation) μ μ± λμ
- 리νλ μ ν ν° μ¬μ© μ μλ‘μ΄ λ¦¬νλ μ ν ν° λ°κΈ
- μ΄μ 리νλ μ ν ν° λ¬΄ν¨ν
ν ν° μ·¨μ(Revocation) λ©μ»€λμ¦ κ΅¬ν
- μ¬μ©μ λ‘κ·Έμμ λλ 보μ μν μ ν ν° λ¬΄ν¨ν κ°λ₯
src/main/java/com/mycom/socket/auth/jwt/JWTUtil.java (1)
Line range hint
109-142: μ€λ³΅ μ½λ μ κ±°κ° νμν©λλ€.
createParser()λ©μλκ° μμμλ μ¬λ¬ λ©μλμμ νμλ₯Ό μ€λ³΅ μμ±νκ³ μμ΅λλ€. μ½λ μ¬μ¬μ©μ±μ λμ΄κΈ° μν΄createParser()λ₯Ό νμ©νμΈμ.λ€μκ³Ό κ°μ΄ μμ νλ κ²μ μ μν©λλ€:
private Date getExpirationFromToken(String token) { - return Jwts.parser() - .verifyWith(secretKey) - .build() + return createParser() .parseSignedClaims(token) .getPayload() .getExpiration(); } public String getEmail(String token) { - return Jwts.parser() - .verifyWith(secretKey) - .requireIssuer(jwtProperties.getIssuer()) - .build() + return createParser() .parseSignedClaims(token) .getPayload() .getSubject(); }
π Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
π Files selected for processing (5)
src/main/java/com/mycom/socket/auth/controller/RefreshController.java(1 hunks)src/main/java/com/mycom/socket/auth/jwt/JWTUtil.java(2 hunks)src/main/java/com/mycom/socket/auth/security/CookieUtil.java(1 hunks)src/main/java/com/mycom/socket/auth/security/LoginFilter.java(3 hunks)src/main/java/com/mycom/socket/auth/service/AuthService.java(4 hunks)
π Additional comments (7)
src/main/java/com/mycom/socket/auth/security/CookieUtil.java (2)
39-46: ꡬνμ΄ κΉλνκ³ μ μ ν©λλ€!κ³΅ν΅ λ©μλλ₯Ό νμ©νμ¬ μ€λ³΅ μ½λλ₯Ό μ κ±°νκ³ , λͺ νν λ§€κ°λ³μ μ λ¬λ‘ κ°λ μ±μ΄ μ’μ΅λλ€.
71-72: λ§λ£λ μΏ ν€ μ²λ¦¬κ° μ μ ν©λλ€!보μ νλκ·Έλ₯Ό κ°μ λ‘ trueλ‘ μ€μ νκ³ κ³΅ν΅ λ©μλλ₯Ό νμ©νμ¬ μμ νκ² κ΅¬νλμμ΅λλ€.
src/main/java/com/mycom/socket/auth/service/AuthService.java (2)
32-32: μμ‘΄μ± μ£Όμ μ΄ μ¬λ°λ₯΄κ² ꡬνλμμ΅λλ€!final ν€μλμ μμ±μ μ£Όμ λ°©μμ μ¬μ©νμ¬ λΆλ³μ±μ 보μ₯νκ³ μμ΅λλ€.
51-61: ν ν° λ§λ£ μκ° κ²μ¦μ΄ νμν©λλ€.리νλ μ ν ν°κ³Ό μ‘μΈμ€ ν ν°μ ꡬνμ΄ μ λμ΄μμΌλ, 보μμ μν΄ λ€μ μ¬νλ€μ νμΈν΄μ£ΌμΈμ:
- 리νλ μ ν ν°μ μ ν¨ κΈ°κ°μ΄ μ‘μΈμ€ ν ν°λ³΄λ€ κΈΈκ² μ€μ λμ΄ μλμ§ νμΈ
- ν ν° λ§λ£ μκ°μ΄ 보μ μ μ± μ λΆν©νλμ§ κ²ν
src/main/java/com/mycom/socket/auth/security/LoginFilter.java (1)
32-32: μμ‘΄μ± μ£Όμ μ΄ μ¬λ°λ₯΄κ² ꡬνλμμ΅λλ€!JWTPropertiesλ₯Ό ν΅ν ν ν° μ ν¨ κΈ°κ° μ€μ μ΄ μ ꡬνλμμ΅λλ€.
src/main/java/com/mycom/socket/auth/controller/RefreshController.java (2)
19-25: μμ‘΄μ± μ£Όμ μ΄ μ μ ν ꡬμ±λμμ΅λλ€!νμν λͺ¨λ μμ‘΄μ±μ΄ μμ±μ μ£Όμ μ ν΅ν΄ μ¬λ°λ₯΄κ² μ£Όμ λμμ΅λλ€.
51-60: 리νλ μ ν ν° μΆμΆ λ‘μ§μ΄ μ ꡬνλμμ΅λλ€!Optionalμ νμ©ν null μ²λ¦¬μ ν¨μν νλ‘κ·Έλλ° μ€νμΌμ΄ μ μ μ©λμμ΅λλ€.
| private Cookie createCookie(String name, String value, long maxAge, boolean secure) { | ||
| Cookie cookie = new Cookie(name, value); | ||
| cookie.setHttpOnly(true); | ||
| cookie.setSecure(jwtProperties.isSecureCookie()); | ||
| cookie.setSecure(secure); | ||
| cookie.setPath("/"); | ||
| cookie.setMaxAge((int) jwtProperties.getAccessTokenValidityInSeconds()); | ||
| cookie.setMaxAge((int) maxAge); | ||
| return cookie; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π οΈ Refactor suggestion
보μ κ°νλ₯Ό μν SameSite μμ± μΆκ° νμ
μΏ ν€ μμ± μ CSRF 곡격 λ°©μ§λ₯Ό μν΄ SameSite μμ±μ μΆκ°νλ κ²μ΄ μ’μ΅λλ€.
private Cookie createCookie(String name, String value, long maxAge, boolean secure) {
Cookie cookie = new Cookie(name, value);
cookie.setHttpOnly(true);
cookie.setSecure(secure);
cookie.setPath("/");
cookie.setMaxAge((int) maxAge);
+ cookie.setAttribute("SameSite", "Strict");
return cookie;
}π Committable suggestion
βΌοΈ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private Cookie createCookie(String name, String value, long maxAge, boolean secure) { | |
| Cookie cookie = new Cookie(name, value); | |
| cookie.setHttpOnly(true); | |
| cookie.setSecure(jwtProperties.isSecureCookie()); | |
| cookie.setSecure(secure); | |
| cookie.setPath("/"); | |
| cookie.setMaxAge((int) jwtProperties.getAccessTokenValidityInSeconds()); | |
| cookie.setMaxAge((int) maxAge); | |
| return cookie; | |
| } | |
| private Cookie createCookie(String name, String value, long maxAge, boolean secure) { | |
| Cookie cookie = new Cookie(name, value); | |
| cookie.setHttpOnly(true); | |
| cookie.setSecure(secure); | |
| cookie.setPath("/"); | |
| cookie.setMaxAge((int) maxAge); | |
| cookie.setAttribute("SameSite", "Strict"); | |
| return cookie; | |
| } |
| * Access Tokenκ³Ό Refresh Token μΏ ν€λ₯Ό 무ν¨ννμ¬ λ‘κ·Έμμμ μνν©λλ€. | ||
| * | ||
| * @param response HTTP μλ΅ κ°μ²΄ (μΏ ν€ λ¬΄ν¨νμ©) | ||
| * @param response HTTP μλ΅ κ°μ²΄ | ||
| */ | ||
| public void logout(HttpServletResponse response) { | ||
| response.addCookie(cookieUtil.createExpiredAuthCookie()); // CookieUtil μ¬μ© | ||
| response.addCookie(cookieUtil.createExpiredCookie(jwtProperties.getAccessTokenCookieName())); | ||
| response.addCookie(cookieUtil.createExpiredCookie(jwtProperties.getRefreshTokenCookieName())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π οΈ Refactor suggestion
λ‘κ·Έμμ 보μ κ°νκ° νμν©λλ€.
ν ν° λ¬΄ν¨ν ꡬνμ΄ μ λμ΄μμΌλ, λ€μκ³Ό κ°μ 보μ κ°μ μ¬νμ κ³ λ €ν΄μ£ΌμΈμ:
- μΏ ν€ μμ± κ²μ¦ (Secure, HttpOnly, SameSite)
- μλ² μΈ‘ ν ν° λΈλ리μ€νΈ κ΄λ¦¬ μΆκ°
public void logout(HttpServletResponse response) {
+ // μλ² μΈ‘ ν ν° λΈλ리μ€νΈμ μΆκ°νλ λ‘μ§
+ tokenBlacklistService.addToBlacklist(getCurrentAccessToken());
+ tokenBlacklistService.addToBlacklist(getCurrentRefreshToken());
+
response.addCookie(cookieUtil.createExpiredCookie(jwtProperties.getAccessTokenCookieName()));
response.addCookie(cookieUtil.createExpiredCookie(jwtProperties.getRefreshTokenCookieName()));
}Committable suggestion skipped: line range outside the PR's diff.
| // JWT ν ν° μμ± | ||
| String accessToken = jwtUtil.createToken( | ||
| member.getEmail(), | ||
| jwtProperties.getAccessTokenValidityInSeconds() | ||
| ); | ||
| String refreshToken = jwtUtil.createToken( | ||
| member.getEmail(), | ||
| jwtProperties.getRefreshTokenValidityInSeconds() | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π οΈ Refactor suggestion
ν ν° λ³΄μ κ°νκ° νμν©λλ€.
νμ¬ μ‘μΈμ€ ν ν°κ³Ό 리νλ μ ν ν°μ΄ λμΌν ν΄λ μμ μ¬μ©νκ³ μμ΅λλ€. 보μ κ°νλ₯Ό μν΄ λ€μ μ¬νλ€μ κ³ λ €ν΄μ£ΌμΈμ:
- 리νλ μ ν ν°μλ μ΅μνμ ν΄λ μλ§ ν¬ν¨
- ν ν° νμ μ ꡬλΆνλ ν΄λ μ μΆκ°
- 리νλ μ ν ν°μ μλ³μλ₯Ό λ°μ΄ν°λ² μ΄μ€μ μ μ₯νμ¬ μΆμ
String accessToken = jwtUtil.createToken(
member.getEmail(),
- jwtProperties.getAccessTokenValidityInSeconds()
+ jwtProperties.getAccessTokenValidityInSeconds(),
+ Map.of("type", "ACCESS_TOKEN")
);
String refreshToken = jwtUtil.createToken(
member.getEmail(),
- jwtProperties.getRefreshTokenValidityInSeconds()
+ jwtProperties.getRefreshTokenValidityInSeconds(),
+ Map.of("type", "REFRESH_TOKEN")
);Committable suggestion skipped: line range outside the PR's diff.
| Cookie accessTokenCookie = cookieUtil.createAuthCookie(accessToken); //μ‘μΈμ€ ν ν° μΏ ν€ | ||
| Cookie refreshTokenCookie = cookieUtil.createRefreshCookie(refreshToken); //리νλ μ ν ν° μΏ ν€ | ||
| response.addCookie(accessTokenCookie); | ||
| response.addCookie(refreshTokenCookie); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π‘ Codebase verification
μΏ ν€ λ³΄μ μ€μ μ΄ λΆμμ ν©λλ€.
λ€μκ³Ό κ°μ 보μ λ¬Έμ κ° λ°κ²¬λμμ΅λλ€:
- SameSite μμ±μ΄ μ€μ λμ΄ μμ§ μμ CSRF 곡격μ μ·¨μ½ν μ μμ΅λλ€.
- Pathκ° "/"λ‘ μ€μ λμ΄ μμ΄ νμ μ΄μμΌλ‘ λμ λ²μμ μΏ ν€κ° λ ΈμΆλ©λλ€.
μμ μ΄ νμν νμΌ:
src/main/java/com/mycom/socket/auth/security/CookieUtil.java
κΆμ₯ μ¬ν:
- SameSite=Strict λλ Lax μμ± μΆκ°
- μ ν리μΌμ΄μ μ μ€μ μꡬμ¬νμ λ§κ² Path λ²μ μΆμ
- μ΄μ νκ²½μμ Secure νλκ·Έκ° λ°λμ νμ±νλλλ‘ μ€μ κ²ν
π Analysis chain
μΏ ν€ λ³΄μ μ€μ μ νμΈν΄μ£ΌμΈμ.
μΏ ν€ μμ± μ λ€μ 보μ μμ±λ€μ΄ μ¬λ°λ₯΄κ² μ€μ λμ΄ μλμ§ νμΈμ΄ νμν©λλ€:
- HttpOnly
- Secure
- SameSite
- Path
π Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# CookieUtilμ ꡬνμ νμΈνμ¬ λ³΄μ μ€μ κ²μ¦
rg -A 10 "createAuthCookie|createRefreshCookie" --type java
Length of output: 8548
Script:
#!/bin/bash
# Check createCookie implementation and JwtProperties
echo "=== CookieUtil.createCookie implementation ==="
ast-grep --pattern 'createCookie($$$) { $$$ }'
echo -e "\n=== JwtProperties configuration ==="
rg -A 5 "class JwtProperties" --type java
Length of output: 282
Script:
#!/bin/bash
# μΏ ν€ μμ± λ©μλμ μ€μ νμΌ κ²μ
echo "=== Cookie creation implementation ==="
rg -A 15 "private.*createCookie" --type java
echo -e "\n=== Security and JWT configuration ==="
fd -e yml -e properties -e yaml | xargs rg -l "jwt|cookie|secure"
Length of output: 1755
| @PostMapping("/refresh") | ||
| public TokenResponse refreshAccessToken(HttpServletRequest request, HttpServletResponse response) { | ||
| String refreshToken = extractRefreshToken(request) | ||
| .orElseThrow(() -> new BadRequestException("리νλ μ ν ν°μ΄ μμ΅λλ€. λ€μ λ‘κ·ΈμΈν΄μ£ΌμΈμ.")); | ||
|
|
||
| try { | ||
| if (!jwtUtil.validateToken(refreshToken)) { | ||
| throw new JwtException("Invalid refresh token"); | ||
| } | ||
|
|
||
| String email = jwtUtil.getEmail(refreshToken); | ||
| String newAccessToken = jwtUtil.createToken(email, jwtProperties.getAccessTokenValidityInSeconds()); | ||
| String newRefreshToken = jwtUtil.createToken(email, jwtProperties.getRefreshTokenValidityInSeconds()); | ||
|
|
||
| response.addCookie(cookieUtil.createAuthCookie(newAccessToken)); | ||
| response.addCookie(cookieUtil.createRefreshCookie(newRefreshToken)); | ||
|
|
||
| return TokenResponse.of(newAccessToken); | ||
| } catch (JwtException e) { | ||
| response.addCookie(cookieUtil.createExpiredCookie(jwtProperties.getRefreshTokenCookieName())); | ||
| throw new BadRequestException("μ ν¨νμ§ μμ 리νλ μ ν ν°μ λλ€. λ€μ λ‘κ·ΈμΈν΄μ£ΌμΈμ."); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
보μ κ°μ μ΄ νμν©λλ€.
λ€μ 보μ κ΄λ ¨ μ¬νλ€μ κ³ λ €ν΄μ£ΌμΈμ:
- CSRF 보νΈκ° νμν©λλ€.
@CrossOriginμ€μ μ΄λ CSRF ν ν° κ²μ¦μ μΆκ°νμΈμ. - ν ν° μ¬λ°κΈ μλν¬μΈνΈμ λν rate limitingμ΄ νμν©λλ€.
- ν ν° μ¬λ°κΈ μλ μ€ν¨μ λν κ°μ¬(audit) λ‘κΉ μ΄ νμν©λλ€.
λ€μκ³Ό κ°μ΄ rate limitingμ μΆκ°νλ κ²μ μ μν©λλ€:
+ @RateLimiter(name = "refreshToken", fallbackMethod = "refreshTokenFallback")
@PostMapping("/refresh")
public TokenResponse refreshAccessToken(...)κ·Έλ¦¬κ³ μ€ν¨ν μλμ λν λ‘κΉ μ μΆκ°νμΈμ:
} catch (JwtException e) {
+ log.warn("Failed refresh token attempt for IP: {}", request.getRemoteAddr());
response.addCookie(cookieUtil.createExpiredCookie(jwtProperties.getRefreshTokenCookieName()));
throw new BadRequestException("μ ν¨νμ§ μμ 리νλ μ ν ν°μ
λλ€. λ€μ λ‘κ·ΈμΈν΄μ£ΌμΈμ.");
}Committable suggestion skipped: line range outside the PR's diff.
| public boolean validateToken(String token) { | ||
| try { | ||
| if (!StringUtils.hasText(token)) { | ||
| return false; | ||
| } | ||
| if (!StringUtils.hasText(token)) { | ||
| return false; | ||
| } | ||
|
|
||
| try { | ||
| Jwts.parser() | ||
| .verifyWith(secretKey) | ||
| .requireIssuer(jwtProperties.getIssuer()) | ||
| .build() | ||
| .parseSignedClaims(token); | ||
| return true; | ||
| } catch (Exception e) { | ||
| log.warn("JWT ν ν° κ²μ¦ μ€ν¨", e); | ||
|
|
||
| Date expiration = getExpirationFromToken(token); | ||
| return new Date().before(expiration); | ||
| } catch (ExpiredJwtException e) { | ||
| log.warn("λ§λ£λ JWT ν ν°μ λλ€."); | ||
| return false; | ||
| } catch (JwtException e) { | ||
| log.warn("μ ν¨νμ§ μμ JWT ν ν°μ λλ€.", e); | ||
| return false; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π οΈ Refactor suggestion
ν ν° κ²μ¦ λ‘μ§ κ°νκ° νμν©λλ€.
νμ¬ κ΅¬νμ κΈ°λ³Έμ μΈ κ²μ¦λ§ μννκ³ μμ΅λλ€. λ€μ κ²μ¦ νλͺ©λ€μ μΆκ°νλ κ²μ κΆμ₯ν©λλ€:
- ν ν° νμ κ²μ¦
- NBF (Not Before) ν΄λ μ κ²μ¦
- ν ν° λΈλ리μ€νΈ νμΈ
λ€μκ³Ό κ°μ΄ μμ νλ κ²μ μ μν©λλ€:
public boolean validateToken(String token) {
try {
- Jwts.parser()
+ var claims = Jwts.parser()
.verifyWith(secretKey)
.requireIssuer(jwtProperties.getIssuer())
.build()
- .parseSignedClaims(token);
+ .parseSignedClaims(token)
+ .getPayload();
+
+ // ν ν° νμ
κ²μ¦
+ String tokenType = claims.get("type", String.class);
+ if (tokenType == null) {
+ return false;
+ }
+
+ // NBF κ²μ¦
+ Date nbf = claims.getNotBefore();
+ if (nbf != null && nbf.after(new Date())) {
+ return false;
+ }Committable suggestion skipped: line range outside the PR's diff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
β»οΈ Duplicate comments (1)
src/main/java/com/mycom/socket/auth/service/AuthService.java (1)
109-115:β οΈ Potential issueλ‘κ·Έμμ 보μ κ°νκ° νμν©λλ€.
μ΄μ 리뷰μμ μ κΈ°λ 보μ λ¬Έμ κ° μμ§ ν΄κ²°λμ§ μμμ΅λλ€. νμ¬ κ΅¬νμ ν΄λΌμ΄μΈνΈ μΈ‘ μΏ ν€λ§ 무ν¨ννκ³ μμ΄, νμ·¨λ ν ν°μ μ μ© κ°λ₯μ±μ΄ μμ΅λλ€.
λ€μ 보μ κ°μ μ¬νμ λ°λμ ꡬνν΄μ£ΌμΈμ:
- μλ² μΈ‘ ν ν° λΈλ리μ€νΈ κ΄λ¦¬
- μΏ ν€ λ³΄μ μμ± μ€μ (Secure, HttpOnly, SameSite)
public void logout(HttpServletResponse response) { + // μλ² μΈ‘ ν ν° λΈλ리μ€νΈμ μΆκ° + String currentAccessToken = getCurrentAccessToken(); + String currentRefreshToken = getCurrentRefreshToken(); + + if (currentAccessToken != null) { + tokenBlacklistService.addToBlacklist(currentAccessToken); + } + if (currentRefreshToken != null) { + tokenBlacklistService.addToBlacklist(currentRefreshToken); + } + response.addCookie(cookieUtil.createExpiredCookie(jwtProperties.getAccessTokenCookieName())); response.addCookie(cookieUtil.createExpiredCookie(jwtProperties.getRefreshTokenCookieName())); }
π§Ή Nitpick comments (4)
src/main/java/com/mycom/socket/auth/security/CookieUtil.java (1)
23-31: μΏ ν€ λ³΄μ μ€μ κ°μ νμ보μμ± κ°νλ₯Ό μν΄ λ€μ μ¬νλ€μ κ³ λ €ν΄μ£ΌμΈμ:
- μΏ ν€μ λλ©μΈμ λͺ μμ μΌλ‘ μ€μ νλ κ²μ΄ μ’μ΅λλ€.
- νλ‘λμ νκ²½μμλ secure νλκ·Έλ₯Ό νμ trueλ‘ μ€μ νλ κ²μ΄ κΆμ₯λ©λλ€.
private Cookie createCookie(String name, String value, long maxAge, boolean secure) { Cookie cookie = new Cookie(name, value); cookie.setHttpOnly(true); cookie.setSecure(secure); cookie.setPath("/api/auth"); + cookie.setDomain(jwtProperties.getCookieDomain()); cookie.setMaxAge((int) maxAge); cookie.setAttribute("SameSite", "Strict"); return cookie; }src/main/java/com/mycom/socket/auth/service/AuthService.java (1)
Line range hint
1-117: JWT 리νλ μ ꡬνμ μν μΆκ° κΈ°λ₯μ΄ νμν©λλ€.νμ¬ κ΅¬νμμ λ€μκ³Ό κ°μ μ€μν κΈ°λ₯λ€μ΄ λλ½λμ΄ μμ΅λλ€:
- ν ν° λ¦¬νλ μ μλν¬μΈνΈ
- ν ν° λ§λ£ νμΈ λ° μ²λ¦¬
- 리νλ μ ν ν° μ¬μ¬μ© κ°μ§
λ€μκ³Ό κ°μ λ©μλλ€μ μΆκ° ꡬνμ μ μλ립λλ€:
/** * μ‘μΈμ€ ν ν° κ°±μ * μ ν¨ν 리νλ μ ν ν°μ μ¬μ©νμ¬ μλ‘μ΄ μ‘μΈμ€ ν ν° λ°κΈ */ public String refreshAccessToken(String refreshToken) { // 리νλ μ ν ν° μ ν¨μ± κ²μ¦ // ν ν° μ¬μ¬μ© νμΈ // μλ‘μ΄ μ‘μΈμ€ ν ν° λ°κΈ } /** * ν ν° μ ν¨μ± κ²μ¦ * ν ν°μ μλͺ , λ§λ£ μ¬λΆ, λΈλ리μ€νΈ μ¬λΆ νμΈ */ public boolean validateToken(String token, String tokenType) { // ν ν° μ ν¨μ± κ²μ¦ λ‘μ§ }src/main/java/com/mycom/socket/auth/dto/response/TokenResponse.java (1)
3-11: λΆνμν νλ μ κ±° κΆμ₯
successνλλaccessTokenμ΄ nullμΈμ§ μ¬λΆλ‘ νλ¨ν μ μμ΄ μ€λ³΅λ©λλ€. λ€μκ³Ό κ°μ΄ μμ νλ κ²μ μ μν©λλ€:public record TokenResponse( String accessToken, - String message, - boolean success + String message ) { public static TokenResponse of(String message) { - return new TokenResponse(null, message, false); + return new TokenResponse(null, message); } }src/main/java/com/mycom/socket/auth/jwt/JWTUtil.java (1)
123-130: ν ν° λ§λ£ μκ° μΆμΆ λ©μλ κ°μ νμ
getExpirationFromTokenλ©μλκ°createParser()λ₯Ό μ¬μ©νμ§ μμ μΌκ΄μ±μ΄ λ¨μ΄μ§λλ€. λ€μκ³Ό κ°μ΄ μμ νλ κ²μ μ μν©λλ€:private Date getExpirationFromToken(String token) { - return Jwts.parser() - .verifyWith(secretKey) - .build() + return createParser() .parseSignedClaims(token) .getPayload() .getExpiration(); }
π Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
π Files selected for processing (7)
src/main/java/com/mycom/socket/auth/controller/RefreshController.java(1 hunks)src/main/java/com/mycom/socket/auth/dto/response/TokenResponse.java(1 hunks)src/main/java/com/mycom/socket/auth/jwt/JWTUtil.java(2 hunks)src/main/java/com/mycom/socket/auth/security/CookieUtil.java(1 hunks)src/main/java/com/mycom/socket/auth/security/LoginFilter.java(3 hunks)src/main/java/com/mycom/socket/auth/service/AuthService.java(4 hunks)src/test/java/com/mycom/socket/member/service/LoginTest.java(4 hunks)
π Additional comments (8)
src/main/java/com/mycom/socket/auth/security/LoginFilter.java (3)
4-4: ꡬ쑰μ λ³κ²½μ΄ μ μ΄λ£¨μ΄μ‘μ΅λλ€!JWT μμ± κ΄λ¦¬λ₯Ό μν μμ‘΄μ± μ£Όμ μ΄ κΉλνκ² κ΅¬νλμμ΅λλ€.
Also applies to: 32-32
69-72: μΏ ν€ λ³΄μ μ€μ μ΄ λΆμμ ν©λλ€.μ΄μ 리뷰μμ μ§μ λ μΏ ν€ λ³΄μ λ¬Έμ κ° μμ§ ν΄κ²°λμ§ μμμ΅λλ€:
- SameSite μμ± λ―Έμ€μ
- Pathκ° λ무 κ΄λ²μνκ² μ€μ λ¨
λ€μ 보μ μ€μ μ μΆκ°ν΄μ£ΌμΈμ:
- SameSite=Strict μ€μ
- Path λ²μ μ΅μν
- Secure νλκ·Έ νμ±ν
56-66:β οΈ Potential issueν ν° μμ± λ‘μ§ κ°μ μ΄ νμν©λλ€.
λ€μκ³Ό κ°μ κ°μ μ¬νμ μ μλ립λλ€:
- ν ν° νμ μ λ¬Έμμ΄ λ¦¬ν°λ΄ λμ μμλ enumμΌλ‘ μ μ
- μ‘μΈμ€ ν ν°κ³Ό 리νλ μ ν ν°μ ν΄λ μμ λΆλ¦¬νμ¬ λ³΄μ κ°ν
λ€μκ³Ό κ°μ΄ μμ ν΄μ£ΌμΈμ:
+private static final String TOKEN_TYPE_ACCESS = "ACCESS_TOKEN"; +private static final String TOKEN_TYPE_REFRESH = "REFRESH_TOKEN"; String accessToken = jwtUtil.createToken( member.getEmail(), jwtProperties.getAccessTokenValidityInSeconds(), - "ACCESS_TOKEN" + TOKEN_TYPE_ACCESS, + Map.of("roles", member.getRoles()) // μ‘μΈμ€ ν ν°μλ§ κΆν μ 보 ν¬ν¨ ); String refreshToken = jwtUtil.createToken( member.getEmail(), jwtProperties.getRefreshTokenValidityInSeconds(), - "REFRESH_TOKEN" + TOKEN_TYPE_REFRESH );Likely invalid or redundant comment.
src/main/java/com/mycom/socket/auth/security/CookieUtil.java (2)
34-47: ꡬνμ΄ μ λμμ΅λλ€!λ©μλκ° μ λ¬Έμνλμ΄ μκ³ , κ³΅ν΅ λ‘μ§μ μ μ ν μ¬μ¬μ©νκ³ μμ΅λλ€.
65-73: ꡬνμ΄ μ μ ν©λλ€!λ§λ£λ μΏ ν€ μμ± μ 보μ μ€μ μ΄ μ μ νκ² λμ΄μκ³ , λ¬Έμνλ μ λμ΄μμ΅λλ€.
src/main/java/com/mycom/socket/auth/service/AuthService.java (1)
32-32: μμ‘΄μ± μ£Όμ μ΄ μ¬λ°λ₯΄κ² ꡬνλμμ΅λλ€.
JWTPropertiesλ₯Ό ν΅ν ν ν° μ€μ κ΄λ¦¬κ° μ ꡬ쑰νλμ΄ μμ΅λλ€.src/main/java/com/mycom/socket/auth/controller/RefreshController.java (2)
35-39:β οΈ Potential issueν ν° κ²μ¦ μ€ν¨ μ λ‘κΉ μΆκ° νμ
보μ κ°μ¬λ₯Ό μν΄ μ€ν¨ν ν ν° κ²μ¦ μλλ₯Ό λ‘κΉ ν΄μΌ ν©λλ€:
String refreshToken = refreshTokenOpt.get(); if (!jwtUtil.validateToken(refreshToken, "REFRESH_TOKEN")) { + log.warn("Invalid refresh token attempt from IP: {}", request.getRemoteAddr()); response.addCookie(cookieUtil.createExpiredCookie(jwtProperties.getRefreshTokenCookieName())); return TokenResponse.of("μ ν¨νμ§ μμ 리νλ μ ν ν°μ λλ€. λ€μ λ‘κ·ΈμΈν΄μ£ΌμΈμ."); }Likely invalid or redundant comment.
28-29:β οΈ Potential issue보μ κ°ν νμ
λ€μ 보μ κΈ°λ₯λ€μ μΆκ°ν΄μΌ ν©λλ€:
- CSRF 보νΈ
- Rate limiting
- μ€ν¨ν ν ν° κ°±μ μλμ λν κ°μ¬ λ‘κΉ
+@Slf4j +@RateLimiter(name = "refreshToken") @PostMapping("/refresh") public TokenResponse refreshAccessToken(...)Likely invalid or redundant comment.
| /** | ||
| * Refresh Tokenμ μ μ₯νλ μΏ ν€ μμ± | ||
| * Access Token μ¬λ°κΈμ μ¬μ©λλ Refresh Tokenμ μΏ ν€μ μ μ₯ν©λλ€. | ||
| * | ||
| * @param token JWT Refresh Token λ¬Έμμ΄ | ||
| * @return Refresh Tokenμ΄ μ μ₯λ μΏ ν€ | ||
| */ | ||
| public Cookie createRefreshCookie(String token) { | ||
| return createCookie( | ||
| jwtProperties.getRefreshTokenCookieName(), | ||
| token, | ||
| jwtProperties.getRefreshTokenValidityInSeconds(), | ||
| jwtProperties.isSecureCookie() | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π‘ Codebase verification
ν ν° μ ν¨κΈ°κ° κ²μ¦ λ‘μ§ κ΅¬ν λ°©μ
리νλ μ ν ν°μ μ ν¨ κΈ°κ°μ΄ μ‘μΈμ€ ν ν°λ³΄λ€ κΈΈλλ‘ λ³΄μ₯νκΈ° μν΄ λ€μκ³Ό κ°μ κ²μ¦ λ‘μ§ μΆκ°λ₯Ό μ μλ립λλ€:
@PostConstructλ₯Ό μ¬μ©νμ¬ μ΄κΈ°ν μμ μ ν ν° μ ν¨κΈ°κ°μ κ²μ¦- Bean Validationμ νμ©νμ¬ κ° νλμ μ΅μκ° κ²μ¦ (
@Min) refreshTokenValidityInSecondsκ°accessTokenValidityInSecondsλ³΄λ€ ν°μ§ νμΈνλ 컀μ€ν μ ν¨μ± κ²μ¬ μΆκ°
π Analysis chain
리νλ μ ν ν° μ ν¨ κΈ°κ° κ²μ¦ μΆκ° κ³ λ €
리νλ μ ν ν°μ μ ν¨ κΈ°κ°μ΄ μ‘μΈμ€ ν ν°λ³΄λ€ κΈΈκ² μ€μ λμλμ§ νμΈνλ κ²μ¦ λ‘μ§ μΆκ°λ₯Ό κ³ λ €ν΄λ³΄μΈμ.
π Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# JWTPropertiesμμ ν ν° μ ν¨ κΈ°κ° μ€μ μ νμΈ
rg "tokenValidityInSeconds|refreshTokenValidityInSeconds" -A 2
Length of output: 383
Script:
#!/bin/bash
# Get complete JWTProperties class
cat src/main/java/com/mycom/socket/auth/config/JWTProperties.java
# Check application properties/yaml files
fd -e yml -e yaml -e properties --exec cat {}
Length of output: 1162
| String refreshToken = jwtUtil.createToken(member.getEmail(), | ||
| jwtProperties.getRefreshTokenValidityInSeconds(), "ACCESS_TOKEN"); | ||
|
|
||
| Cookie refreshTokenCookie = cookieUtil.createRefreshCookie(refreshToken); | ||
| response.addCookie(refreshTokenCookie); | ||
|
|
||
| String accessToken = jwtUtil.createToken(member.getEmail(), | ||
| jwtProperties.getAccessTokenValidityInSeconds(), "ACCESS_TOKEN"); | ||
|
|
||
| Cookie accessTokenCookie = cookieUtil.createAuthCookie(accessToken); | ||
| response.addCookie(accessTokenCookie); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ν ν° μμ± λ‘μ§μ μ€μν 보μ λ¬Έμ κ° μμ΅λλ€.
λ€μκ³Ό κ°μ λ¬Έμ μ λ€μ΄ λ°κ²¬λμμ΅λλ€:
- 리νλ μ ν ν°μ νμ μ΄ μλͺ» μ€μ λμ΄ μμ΅λλ€ ("ACCESS_TOKEN" λμ "REFRESH_TOKEN" μ¬μ© νμ)
- ν ν° μμ± μμκ° λ³΄μμ μ·¨μ½ν μ μμ΅λλ€ (μ‘μΈμ€ ν ν°μ λ¨Όμ μμ±νλ κ²μ΄ κΆμ₯λ¨)
λ€μκ³Ό κ°μ΄ μμ ν΄μ£ΌμΈμ:
- String refreshToken = jwtUtil.createToken(member.getEmail(),
- jwtProperties.getRefreshTokenValidityInSeconds(), "ACCESS_TOKEN");
-
- Cookie refreshTokenCookie = cookieUtil.createRefreshCookie(refreshToken);
- response.addCookie(refreshTokenCookie);
-
- String accessToken = jwtUtil.createToken(member.getEmail(),
- jwtProperties.getAccessTokenValidityInSeconds(), "ACCESS_TOKEN");
-
- Cookie accessTokenCookie = cookieUtil.createAuthCookie(accessToken);
- response.addCookie(accessTokenCookie);
+ // λ¨Όμ μ‘μΈμ€ ν ν° μμ±
+ String accessToken = jwtUtil.createToken(member.getEmail(),
+ jwtProperties.getAccessTokenValidityInSeconds(), "ACCESS_TOKEN");
+ Cookie accessTokenCookie = cookieUtil.createAuthCookie(accessToken);
+
+ // κ·Έ λ€μ 리νλ μ ν ν° μμ±
+ String refreshToken = jwtUtil.createToken(member.getEmail(),
+ jwtProperties.getRefreshTokenValidityInSeconds(), "REFRESH_TOKEN");
+ Cookie refreshTokenCookie = cookieUtil.createRefreshCookie(refreshToken);
+
+ // μΏ ν€ μ€μ
+ response.addCookie(accessTokenCookie);
+ response.addCookie(refreshTokenCookie);π Committable suggestion
βΌοΈ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| String refreshToken = jwtUtil.createToken(member.getEmail(), | |
| jwtProperties.getRefreshTokenValidityInSeconds(), "ACCESS_TOKEN"); | |
| Cookie refreshTokenCookie = cookieUtil.createRefreshCookie(refreshToken); | |
| response.addCookie(refreshTokenCookie); | |
| String accessToken = jwtUtil.createToken(member.getEmail(), | |
| jwtProperties.getAccessTokenValidityInSeconds(), "ACCESS_TOKEN"); | |
| Cookie accessTokenCookie = cookieUtil.createAuthCookie(accessToken); | |
| response.addCookie(accessTokenCookie); | |
| // λ¨Όμ μ‘μΈμ€ ν ν° μμ± | |
| String accessToken = jwtUtil.createToken(member.getEmail(), | |
| jwtProperties.getAccessTokenValidityInSeconds(), "ACCESS_TOKEN"); | |
| Cookie accessTokenCookie = cookieUtil.createAuthCookie(accessToken); | |
| // κ·Έ λ€μ 리νλ μ ν ν° μμ± | |
| String refreshToken = jwtUtil.createToken(member.getEmail(), | |
| jwtProperties.getRefreshTokenValidityInSeconds(), "REFRESH_TOKEN"); | |
| Cookie refreshTokenCookie = cookieUtil.createRefreshCookie(refreshToken); | |
| // μΏ ν€ μ€μ | |
| response.addCookie(accessTokenCookie); | |
| response.addCookie(refreshTokenCookie); |
| String email = jwtUtil.getEmail(refreshToken); | ||
| String newAccessToken = jwtUtil.createToken(email, jwtProperties.getAccessTokenValidityInSeconds(), "ACCESS_TOKEN"); | ||
| String newRefreshToken = jwtUtil.createToken(email, jwtProperties.getRefreshTokenValidityInSeconds(), "REFRESH_TOKEN"); | ||
|
|
||
| response.addCookie(cookieUtil.createAuthCookie(newAccessToken)); | ||
| response.addCookie(cookieUtil.createRefreshCookie(newRefreshToken)); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π‘ Codebase verification
ν ν° κ΄λ¦¬ 보μ μ·¨μ½μ νμΈλ¨
νμ¬ κ΅¬νμμ λ€μκ³Ό κ°μ 보μ μ·¨μ½μ μ΄ λ°κ²¬λμμ΅λλ€:
- μ΄μ 리νλ μ ν ν°μ΄ 무ν¨νλμ§ μμ μ¬μ¬μ© κ°λ₯
- Redisκ° κ΅¬μ±λμ΄ μμΌλ ν ν° κ΄λ¦¬μ νμ©λμ§ μμ
- λμ ν ν° κ°±μ μμ²μ λν μ²λ¦¬ λ‘μ§ λΆμ¬
κΆμ₯ μ¬ν:
- Redisλ₯Ό νμ©ν ν ν° λΈλ리μ€νΈ ꡬν
- 리νλ μ ν ν° μ¬λ°κΈ μ μ΄μ ν ν° μ¦μ 무ν¨ν
- ν ν° λ°κΈ μ΄λ ₯ μΆμ μ μν μ μ₯μ ꡬν
- λμμ± μ μ΄λ₯Ό μν λ½(Lock) λ©μ»€λμ¦ λμ
π Analysis chain
ν ν° μ¬λ°κΈ λ‘μ§ κ²μ¦ νμ
μλ‘μ΄ ν ν° λ°κΈ μ λ€μ μ¬νλ€μ νμΈν΄μΌ ν©λλ€:
- μ΄μ 리νλ μ ν ν° λ¬΄ν¨ν
- ν ν° λ°κΈ μ΄λ ₯ μΆμ
- λμ ν ν° κ°±μ μμ² μ²λ¦¬
π Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check for token revocation implementation
rg -l "TokenRevocationService|BlacklistService|InvalidatedTokenRepository"
Length of output: 76
Script:
#!/bin/bash
# Search for token-related implementations with broader patterns
rg -l "token.*repository|token.*service|token.*cache|token.*store"
# Search for security configurations
rg -l "SecurityConfig|WebSecurityConfig|JwtConfig"
# Search for Redis or cache configurations
rg -l "RedisConfig|CacheConfig|TokenCache"
Length of output: 364
Script:
#!/bin/bash
# Check Redis configuration implementation
cat src/main/java/com/mycom/socket/global/config/RedisConfig.java
# Check security configuration for token handling
cat src/main/java/com/mycom/socket/auth/config/SecurityConfig.java
# Search for Redis usage in token operations
rg -g "!*.{test,spec}.*" "redisTemplate|StringRedisTemplate" -A 3
Length of output: 7750
| @Mock | ||
| private JWTProperties jwtProperties; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π οΈ Refactor suggestion
리νλ μ ν ν° ν μ€νΈ μΌμ΄μ€ 보μ νμ
λ€μ ν μ€νΈ μΌμ΄μ€λ€μ μΆκ°ν΄μΌ ν©λλ€:
- 리νλ μ ν ν° λ§λ£ μλ리μ€
- 리νλ μ ν ν°μ ν΅ν μ‘μΈμ€ ν ν° κ°±μ
- μλͺ»λ 리νλ μ ν ν° μ²λ¦¬
- ν ν° νμ κ²μ¦
@Test
void 리νλ μν ν°_λ§λ£() {
// given
String expiredToken = "expired.refresh.token";
when(jwtUtil.validateToken(expiredToken, "REFRESH_TOKEN")).thenReturn(false);
// when & then
assertThrows(BadRequestException.class,
() -> authService.refreshAccessToken(expiredToken));
}Also applies to: 76-76, 88-89
| public String createToken(String email, long validityInSeconds, String accessToken) { | ||
| Date now = new Date(); | ||
| Date validity = new Date(now.getTime() + | ||
| (jwtProperties.getAccessTokenValidityInSeconds() * 1000)); | ||
|
|
||
| return Jwts.builder() | ||
| .issuer(jwtProperties.getIssuer()) | ||
| .subject(email) | ||
| .issuedAt(now) | ||
| .expiration(validity) | ||
| .signWith(secretKey) | ||
| .compact(); | ||
| Date validity = new Date(now.getTime() + (validityInSeconds * 1000)); | ||
|
|
||
| try { | ||
| return Jwts.builder() | ||
| .issuer(jwtProperties.getIssuer()) | ||
| .subject(email) | ||
| .issuedAt(now) | ||
| .expiration(validity) | ||
| .claim("type", accessToken) | ||
| .signWith(secretKey) | ||
| .compact(); | ||
| } catch (JwtException e) { | ||
| log.error("ν ν° μμ± μ€ μ€λ₯κ° λ°μνμ΅λλ€.", e); | ||
| throw new IllegalStateException("ν ν° μμ± μ€ μ€λ₯κ° λ°μνμ΅λλ€.", e); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π οΈ Refactor suggestion
ν ν° μμ± λ³΄μ κ°ν νμ
ν ν° μμ± μ λ€μ 보μ κΈ°λ₯λ€μ μΆκ°ν΄μΌ ν©λλ€:
- ν ν° ID (jti) ν΄λ μ μΆκ°
- Not Before (nbf) ν΄λ μ μΆκ°
- ν ν° μ©λλ³ μ²μ€(aud) ꡬλΆ
return Jwts.builder()
.issuer(jwtProperties.getIssuer())
.subject(email)
.issuedAt(now)
.expiration(validity)
.claim("type", accessToken)
+ .id(UUID.randomUUID().toString())
+ .notBefore(now)
+ .audience()
+ .add(accessToken.toLowerCase())
+ .and()
.signWith(secretKey)
.compact();π Committable suggestion
βΌοΈ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public String createToken(String email, long validityInSeconds, String accessToken) { | |
| Date now = new Date(); | |
| Date validity = new Date(now.getTime() + | |
| (jwtProperties.getAccessTokenValidityInSeconds() * 1000)); | |
| return Jwts.builder() | |
| .issuer(jwtProperties.getIssuer()) | |
| .subject(email) | |
| .issuedAt(now) | |
| .expiration(validity) | |
| .signWith(secretKey) | |
| .compact(); | |
| Date validity = new Date(now.getTime() + (validityInSeconds * 1000)); | |
| try { | |
| return Jwts.builder() | |
| .issuer(jwtProperties.getIssuer()) | |
| .subject(email) | |
| .issuedAt(now) | |
| .expiration(validity) | |
| .claim("type", accessToken) | |
| .signWith(secretKey) | |
| .compact(); | |
| } catch (JwtException e) { | |
| log.error("ν ν° μμ± μ€ μ€λ₯κ° λ°μνμ΅λλ€.", e); | |
| throw new IllegalStateException("ν ν° μμ± μ€ μ€λ₯κ° λ°μνμ΅λλ€.", e); | |
| } | |
| public String createToken(String email, long validityInSeconds, String accessToken) { | |
| Date now = new Date(); | |
| Date validity = new Date(now.getTime() + (validityInSeconds * 1000)); | |
| try { | |
| return Jwts.builder() | |
| .issuer(jwtProperties.getIssuer()) | |
| .subject(email) | |
| .issuedAt(now) | |
| .expiration(validity) | |
| .claim("type", accessToken) | |
| .id(UUID.randomUUID().toString()) | |
| .notBefore(now) | |
| .audience() | |
| .add(accessToken.toLowerCase()) | |
| .and() | |
| .signWith(secretKey) | |
| .compact(); | |
| } catch (JwtException e) { | |
| log.error("ν ν° μμ± μ€ μ€λ₯κ° λ°μνμ΅λλ€.", e); | |
| throw new IllegalStateException("ν ν° μμ± μ€ μ€λ₯κ° λ°μνμ΅λλ€.", e); | |
| } |
| public boolean validateToken(String token, String expectedType) { | ||
| if (!StringUtils.hasText(token)) { | ||
| return false; | ||
| } | ||
|
|
||
| Jwts.parser() | ||
| try { | ||
| var claims = Jwts.parser() | ||
| .verifyWith(secretKey) | ||
| .requireIssuer(jwtProperties.getIssuer()) | ||
| .build() | ||
| .parseSignedClaims(token); | ||
| return true; | ||
| } catch (Exception e) { | ||
| log.warn("JWT ν ν° κ²μ¦ μ€ν¨", e); | ||
| .parseSignedClaims(token) | ||
| .getPayload(); | ||
|
|
||
| // ν ν° νμ κ²μ¦ | ||
| String tokenType = claims.get("type", String.class); | ||
| if (!expectedType.equals(tokenType)) { | ||
| log.warn("μλͺ»λ ν ν° νμ μ λλ€. expected: {}, actual: {}", expectedType, tokenType); | ||
| return false; | ||
| } | ||
|
|
||
| return new Date().before(claims.getExpiration()); | ||
| } catch (ExpiredJwtException e) { | ||
| log.warn("λ§λ£λ JWT ν ν°μ λλ€."); | ||
| return false; | ||
| } catch (JwtException e) { | ||
| log.warn("μ ν¨νμ§ μμ JWT ν ν°μ λλ€.", e); | ||
| return false; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π‘ Codebase verification
ν ν° λ³΄μ κ°ν κΆμ₯μ¬ν νμΈ
리뷰 μ견μμ μ μλ 보μ κ°ν νλͺ©λ€μ΄ νμ¬ κ΅¬νλμ΄ μμ§ μμμ νμΈνμ΅λλ€. λ€μ μ¬νλ€μ ꡬνμ κΆμ₯λ립λλ€:
-
Redisλ₯Ό νμ©ν ν ν° λΈλ리μ€νΈ ꡬν
- κΈ°μ‘΄ RedisServiceμ ν ν° λ¬΄ν¨ν κΈ°λ₯ μΆκ°
- λ‘κ·Έμμ μ ν ν° λΈλ리μ€νΈ λ±λ‘
-
NBF (Not Before) ν΄λ μ μΆκ°
- JWTUtilμ ν ν° μμ± μ
.notBefore()μ€μ - validateToken() λ©μλμμ μλ κ²μ¦λ¨
- JWTUtilμ ν ν° μμ± μ
-
ν ν° ID (jti) ν΄λ μ μΆκ° λ° μΆμ
- ν ν° μμ± μ κ³ μ ID λΆμ¬
- Redisλ₯Ό νμ©ν΄ μ¬μ©λ ν ν° ID μΆμ
π Analysis chain
ν ν° κ²μ¦ λ‘μ§ κ°ν νμ
λ€μ κ²μ¦ νλͺ©λ€μ μΆκ°ν΄μΌ ν©λλ€:
- ν ν° λΈλ리μ€νΈ νμΈ
- NBF (Not Before) ν΄λ μ κ²μ¦
- ν ν° ID μ€λ³΅ μ¬μ© νμΈ
π Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check for token blacklist implementation
rg -l "TokenBlacklist|RevokedToken|InvalidatedToken"
Length of output: 54
Script:
#!/bin/bash
# Search for Redis/Cache configurations and JWT-related security implementations
rg -l "Redis|Cache|TokenStore"
# Search for JWT claim handling
rg -A 5 "\.claim\(|\.getClaim|jti|nbf"
# Search for security configuration files
fd -e java -e xml -e properties -e yml "Security|Auth|JWT" --type f
Length of output: 1424
Script:
#!/bin/bash
# Check Redis service implementation for token operations
cat src/main/java/com/mycom/socket/global/service/RedisService.java
# Check security configuration
cat src/main/java/com/mycom/socket/auth/config/SecurityConfig.java
Length of output: 5402
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
β»οΈ Duplicate comments (1)
src/main/java/com/mycom/socket/auth/service/AuthService.java (1)
113-119: π οΈ Refactor suggestionλ‘κ·Έμμ 보μ κ°νκ° νμν©λλ€.
ν ν° λ¬΄ν¨ν ꡬνμ΄ μ λμ΄μμΌλ, λ€μκ³Ό κ°μ 보μ κ°μ μ¬νμ κ³ λ €ν΄μ£ΌμΈμ:
- μΏ ν€ μμ± κ²μ¦ (Secure, HttpOnly, SameSite)
- μλ² μΈ‘ ν ν° λΈλ리μ€νΈ κ΄λ¦¬ μΆκ°
public void logout(HttpServletResponse response) { + // μλ² μΈ‘ ν ν° λΈλ리μ€νΈμ μΆκ°νλ λ‘μ§ + tokenBlacklistService.addToBlacklist(getCurrentAccessToken()); + tokenBlacklistService.addToBlacklist(getCurrentRefreshToken()); + response.addCookie(cookieUtil.createExpiredCookie(jwtProperties.getAccessTokenCookieName())); response.addCookie(cookieUtil.createExpiredCookie(jwtProperties.getRefreshTokenCookieName())); }
π Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
π Files selected for processing (1)
src/main/java/com/mycom/socket/auth/service/AuthService.java(4 hunks)
π Additional comments (1)
src/main/java/com/mycom/socket/auth/service/AuthService.java (1)
3-3: μμ‘΄μ± μ£Όμ μ΄ μ μ ν ꡬνλμμ΅λλ€.JWT μ€μ μ κ΄λ¦¬νκΈ° μν JWTProperties μμ‘΄μ± μ£Όμ μ΄ μ λμ΄μμ΅λλ€.
Also applies to: 32-32
| // // λ¨Όμ μ‘μΈμ€ ν ν° μμ± | ||
| String accessToken = jwtUtil.createToken(member.getEmail(), | ||
| jwtProperties.getRefreshTokenValidityInSeconds(), "ACCESS_TOKEN"); | ||
|
|
||
| Cookie refreshTokenCookie = cookieUtil.createRefreshCookie(accessToken); | ||
|
|
||
| // κ·Έ λ€μ 리νλ μ ν ν° μμ± | ||
| String refreshToken = jwtUtil.createToken(member.getEmail(), | ||
| jwtProperties.getAccessTokenValidityInSeconds(), "REFRESH_TOKEN"); | ||
|
|
||
| Cookie accessTokenCookie = cookieUtil.createAuthCookie(refreshToken); | ||
|
|
||
| // μΏ ν€ μ€μ | ||
| response.addCookie(refreshTokenCookie); | ||
| response.addCookie(accessTokenCookie); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
μ¬κ°ν ν ν° μμ± λ‘μ§ μ€λ₯κ° λ°κ²¬λμμ΅λλ€.
λ€μκ³Ό κ°μ μΉλͺ μ μΈ λ¬Έμ μ λ€μ΄ μμ΅λλ€:
- μ‘μΈμ€ ν ν°μ΄ 리νλ μ ν ν°μ μ ν¨ κΈ°κ°μ μ¬μ©νκ³ μμ΅λλ€
- 리νλ μ ν ν°μ΄ μ‘μΈμ€ ν ν°μ μ ν¨ κΈ°κ°μ μ¬μ©νκ³ μμ΅λλ€
- μΏ ν€ μμ± μ ν ν°μ΄ μλͺ» μ λ¬λκ³ μμ΅λλ€
- ν ν° νμ μ΄ μλͺ» μ§μ λμ΄ μμ΅λλ€
λ€μκ³Ό κ°μ΄ μμ ν΄μ£ΌμΈμ:
- // λ¨Όμ μ‘μΈμ€ ν ν° μμ±
- String accessToken = jwtUtil.createToken(member.getEmail(),
- jwtProperties.getRefreshTokenValidityInSeconds(), "ACCESS_TOKEN");
-
- Cookie refreshTokenCookie = cookieUtil.createRefreshCookie(accessToken);
-
- // κ·Έ λ€μ 리νλ μ ν ν° μμ±
- String refreshToken = jwtUtil.createToken(member.getEmail(),
- jwtProperties.getAccessTokenValidityInSeconds(), "REFRESH_TOKEN");
-
- Cookie accessTokenCookie = cookieUtil.createAuthCookie(refreshToken);
-
- // μΏ ν€ μ€μ
- response.addCookie(refreshTokenCookie);
- response.addCookie(accessTokenCookie);
+ // μ‘μΈμ€ ν ν° μμ±
+ String accessToken = jwtUtil.createToken(member.getEmail(),
+ jwtProperties.getAccessTokenValidityInSeconds(), "ACCESS_TOKEN");
+ Cookie accessTokenCookie = cookieUtil.createAuthCookie(accessToken);
+
+ // 리νλ μ ν ν° μμ±
+ String refreshToken = jwtUtil.createToken(member.getEmail(),
+ jwtProperties.getRefreshTokenValidityInSeconds(), "REFRESH_TOKEN");
+ Cookie refreshTokenCookie = cookieUtil.createRefreshCookie(refreshToken);
+
+ // μΏ ν€ μ€μ
+ response.addCookie(accessTokenCookie);
+ response.addCookie(refreshTokenCookie);π Committable suggestion
βΌοΈ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // // λ¨Όμ μ‘μΈμ€ ν ν° μμ± | |
| String accessToken = jwtUtil.createToken(member.getEmail(), | |
| jwtProperties.getRefreshTokenValidityInSeconds(), "ACCESS_TOKEN"); | |
| Cookie refreshTokenCookie = cookieUtil.createRefreshCookie(accessToken); | |
| // κ·Έ λ€μ 리νλ μ ν ν° μμ± | |
| String refreshToken = jwtUtil.createToken(member.getEmail(), | |
| jwtProperties.getAccessTokenValidityInSeconds(), "REFRESH_TOKEN"); | |
| Cookie accessTokenCookie = cookieUtil.createAuthCookie(refreshToken); | |
| // μΏ ν€ μ€μ | |
| response.addCookie(refreshTokenCookie); | |
| response.addCookie(accessTokenCookie); | |
| // μ‘μΈμ€ ν ν° μμ± | |
| String accessToken = jwtUtil.createToken(member.getEmail(), | |
| jwtProperties.getAccessTokenValidityInSeconds(), "ACCESS_TOKEN"); | |
| Cookie accessTokenCookie = cookieUtil.createAuthCookie(accessToken); | |
| // 리νλ μ ν ν° μμ± | |
| String refreshToken = jwtUtil.createToken(member.getEmail(), | |
| jwtProperties.getRefreshTokenValidityInSeconds(), "REFRESH_TOKEN"); | |
| Cookie refreshTokenCookie = cookieUtil.createRefreshCookie(refreshToken); | |
| // μΏ ν€ μ€μ | |
| response.addCookie(accessTokenCookie); | |
| response.addCookie(refreshTokenCookie); |
π‘ Codebase verification
π οΈ Refactor suggestion
리νλ μ ν ν° λ³΄μ μ·¨μ½μ μ΄ νμΈλμμ΅λλ€.
νμ¬ κ΅¬νμμ λ€μκ³Ό κ°μ 보μ μ·¨μ½μ μ΄ λ°κ²¬λμμ΅λλ€:
-
리νλ μ ν ν° μ¬μ¬μ© κ°μ§ λ©μ»€λμ¦μ΄ μμ΅λλ€.
- νμ·¨λ 리νλ μ ν ν°μ μ μ©μ λ°©μ§ν μ μμ΅λλ€.
-
ν ν° κ΅μ²΄(Rotation) μ μ΄μ ν ν°μ 무ν¨νκ° μ΄λ£¨μ΄μ§μ§ μμ΅λλ€.
Memberμν°ν°μupdateRefreshTokenλ§μΌλ‘λ μΆ©λΆν 보μμ μ 곡νμ§ μμ΅λλ€.
-
리νλ μ ν ν° λΈλ리μ€νΈλ ν ν° κ³λ³΄ μΆμ μ΄ κ΅¬νλμ΄ μμ§ μμ΅λλ€.
/refreshμλν¬μΈνΈμμ μλ‘μ΄ ν ν° λ°κΈ μ μ΄μ ν ν°μ μΆμ μ΄ λΆκ°λ₯ν©λλ€.
λ€μ 보μ κΈ°λ₯μ μΆκ°λ₯Ό κΆμ₯λ립λλ€:
- 리νλ μ ν ν° μ¬μ© μ μ΄μ ν ν° μ¦μ 무ν¨ν
- ν ν° κ³λ³΄(family) μΆμ μ ν΅ν μ¬μ¬μ© κ°μ§
- 리νλ μ ν ν° λΈλ리μ€νΈ κ΄λ¦¬
π Analysis chain
리νλ μ ν ν° λ³΄μ κ°νκ° νμν©λλ€.
리νλ μ ν ν°μ 보μμ κ°ννκΈ° μν΄ λ€μ κΈ°λ₯λ€μ ꡬνμ κ³ λ €ν΄μ£ΌμΈμ:
- 리νλ μ ν ν° κ΅μ²΄ (Rotation)
- 리νλ μ ν ν° μ¬μ¬μ© κ°μ§
- 리νλ μ ν ν° λ§λ£ μκ° κ²μ¦
리νλ μ ν ν° κ΅¬νμ΄ μ μ νμ§ νμΈνκΈ° μν΄ λ€μ κ²μ¦μ μ€νν©λλ€:
π Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify refresh token implementation completeness
# Check for token rotation implementation
echo "Checking for token rotation..."
rg -l 'refreshToken.*rotation|rotateRefreshToken|updateRefreshToken'
# Check for token reuse detection
echo "Checking for reuse detection..."
rg -l 'tokenReuse|detectReuse|validateRefreshToken'
# Check for proper token validation
echo "Checking for token validation..."
rg -l 'validateToken|verifyToken|checkToken'
Length of output: 628
Script:
#!/bin/bash
# Check the actual implementation details
echo "=== Member.java refresh token handling ==="
rg -A 10 -B 10 'refreshToken' src/main/java/com/mycom/socket/go_socket/entity/Member.java
echo -e "\n=== RefreshController implementation ==="
cat src/main/java/com/mycom/socket/auth/controller/RefreshController.java
echo -e "\n=== JWTUtil token validation ==="
rg -A 5 'validateToken|verifyToken|checkToken' src/main/java/com/mycom/socket/auth/jwt/JWTUtil.java
Length of output: 4082
Script:
#!/bin/bash
# Get complete JWTUtil implementation
echo "=== Complete JWTUtil implementation ==="
cat src/main/java/com/mycom/socket/auth/jwt/JWTUtil.java
Length of output: 4497
π κ΄λ ¨ μ΄μ
#33 Refresh Token λμ
β¨ κ³Όμ λ΄μ©
리νλ μ ν ν° λμ
πΈ μ€ν¬λ¦°μ·(μ ν)
π λ νΌλ°μ€ (λλ μλ‘ μκ² λ λ΄μ©) νΉμ κΆκΈν μ¬νλ€
Summary by CodeRabbit
μλ‘μ΄ κΈ°λ₯
λ²κ·Έ μμ
리ν©ν λ§