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.
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.
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.
🛠️ 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.
🛠️ 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.
🛠️ 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.
토큰 타입 구분을 위한 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.
🛠️ Refactor suggestion
리프레시 토큰 테스트 케이스 보완이 필요합니다.
현재 테스트는 리프레시 토큰 생성만 검증하고 있습니다. 다음 시나리오에 대한 테스트를 추가해주세요:
- 리프레시 토큰 만료 시나리오
- 리프레시 토큰을 통한 액세스 토큰 갱신
- 잘못된 리프레시 토큰 처리
테스트 케이스 구현에 도움이 필요하시다면 말씀해 주세요.
Also applies to: 88-89
There was a problem hiding this comment.
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.
🛠️ 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.
🛠️ 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.
🛠️ 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.
💡 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.
보안 개선이 필요합니다.
다음 보안 관련 사항들을 고려해주세요:
- 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.
🛠️ 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.
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.
💡 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.
토큰 생성 로직에 중요한 보안 문제가 있습니다.
다음과 같은 문제점들이 발견되었습니다:
- 리프레시 토큰의 타입이 잘못 설정되어 있습니다 ("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.
💡 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.
🛠️ 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.
🛠️ 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.
💡 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.
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.
심각한 토큰 생성 로직 오류가 발견되었습니다.
다음과 같은 치명적인 문제점들이 있습니다:
- 액세스 토큰이 리프레시 토큰의 유효 기간을 사용하고 있습니다
- 리프레시 토큰이 액세스 토큰의 유효 기간을 사용하고 있습니다
- 쿠키 생성 시 토큰이 잘못 전달되고 있습니다
- 토큰 타입이 잘못 지정되어 있습니다
다음과 같이 수정해주세요:
- // 먼저 액세스 토큰 생성
- 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
새로운 기능
버그 수정
리팩토링