Skip to content

Conversation

@ohyuchan123
Copy link
Member

@ohyuchan123 ohyuchan123 commented Jan 14, 2025

πŸ“Œ κ΄€λ ¨ 이슈

#33 Refresh Token λ„μž…

✨ 과제 λ‚΄μš©

λ¦¬ν”„λ ˆμ‹œ 토큰 λ„μž…

πŸ“Έ μŠ€ν¬λ¦°μƒ·(선택)

image

πŸ“š 레퍼런슀 (λ˜λŠ” μƒˆλ‘œ μ•Œκ²Œ 된 λ‚΄μš©) ν˜Ήμ€ κΆκΈˆν•œ 사항듀

Summary by CodeRabbit

  • μƒˆλ‘œμš΄ κΈ°λŠ₯

    • μ•‘μ„ΈμŠ€ 토큰과 λ¦¬ν”„λ ˆμ‹œ 토큰을 μ‚¬μš©ν•œ ν–₯μƒλœ 인증 μ‹œμŠ€ν…œ λ„μž…
    • μƒˆλ‘œ κ³ μΉ¨ 토큰을 μ²˜λ¦¬ν•˜λŠ” 컨트둀러 μΆ”κ°€
    • 토큰 μΏ ν‚€ 관리 κΈ°λŠ₯ κ°œμ„ 
    • 토큰 유효 κΈ°κ°„ μ„ΈλΆ€ μ„€μ • μΆ”κ°€
  • 버그 μˆ˜μ •

    • 이메일 인증 μ½”λ“œ 검증 ν”„λ‘œμ„ΈμŠ€ μ•ˆμ •μ„± κ°œμ„ 
    • Redis μ„œλΉ„μŠ€μ˜ 이메일 확인 둜직 κ°•ν™”
  • λ¦¬νŒ©ν† λ§

    • JWT 토큰 생성 및 관리 방식 μ΅œμ ν™”
    • μΏ ν‚€ 처리 둜직 μ„ΈλΆ„ν™”
    • 인증 ν•„ν„°μ—μ„œ 두 개의 토큰을 μƒμ„±ν•˜λ„λ‘ μ—…λ°μ΄νŠΈ
    • 메일 μ„œλΉ„μŠ€μ˜ μ½”λ“œ 검증 둜직 κ°„μ†Œν™”

@coderabbitai
Copy link

coderabbitai bot commented Jan 14, 2025

Walkthrough

이 ν’€ λ¦¬ν€˜μŠ€νŠΈλŠ” JWT 토큰 인증 μ‹œμŠ€ν…œμ˜ μ£Όμš” λ¦¬νŒ©ν† λ§μ„ ν¬ν•¨ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. μ£Όμš” λ³€κ²½ 사항은 단일 ν† ν°μ—μ„œ μ•‘μ„ΈμŠ€ 및 λ¦¬ν”„λ ˆμ‹œ 토큰 μ‹œμŠ€ν…œμœΌλ‘œμ˜ μ „ν™˜, JWT 속성 ꡬ성 λ³€κ²½, μΏ ν‚€ 관리 κ°œμ„  λ“±μž…λ‹ˆλ‹€. μ΄λŸ¬ν•œ μˆ˜μ •μ€ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ 인증 λ©”μ»€λ‹ˆμ¦˜μ„ λ”μš± μ•ˆμ „ν•˜κ³  μœ μ—°ν•˜κ²Œ λ§Œλ“€κΈ° μœ„ν•œ 포괄적인 μ ‘κ·Ό 방식을 λ‚˜νƒ€λƒ…λ‹ˆλ‹€.

Changes

파일 λ³€κ²½ μš”μ•½
JWTProperties.java κΈ°λ³Έκ°’ 제거, μƒˆ 속성 μΆ”κ°€ (λ¦¬ν”„λ ˆμ‹œ 토큰 유효 μ‹œκ°„, μ•‘μ„ΈμŠ€ 및 λ¦¬ν”„λ ˆμ‹œ 토큰 μΏ ν‚€ 이름)
RefreshController.java μƒˆ 컨트둀러 μΆ”κ°€, μ•‘μ„ΈμŠ€ 토큰 μƒˆλ‘œ κ³ μΉ¨ μ—”λ“œν¬μΈνŠΈ κ΅¬ν˜„
TokenResponse.java μƒˆ λ ˆμ½”λ“œ 생성, μ•‘μ„ΈμŠ€ 토큰을 μœ„ν•œ 정적 νŒ©ν† λ¦¬ λ©”μ„œλ“œ μΆ”κ°€
JWTFilter.java μΏ ν‚€ 이름 확인 둜직 μ—…λ°μ΄νŠΈ
JWTUtil.java μ•‘μ„ΈμŠ€ 및 λ¦¬ν”„λ ˆμ‹œ 토큰 생성 λ©”μ„œλ“œ μΆ”κ°€, μ—λŸ¬ 핸듀링 κ°œμ„ 
CookieUtil.java λ¦¬ν”„λ ˆμ‹œ ν† ν°μš© μΏ ν‚€ 생성 및 만료 λ©”μ„œλ“œ μΆ”κ°€
LoginFilter.java 토큰 및 μΏ ν‚€ 생성 둜직 이쀑 토큰 μ‹œμŠ€ν…œμœΌλ‘œ λ³€κ²½
AuthService.java 둜그인 및 λ‘œκ·Έμ•„μ›ƒμ—μ„œ 이쀑 토큰 μ‹œμŠ€ν…œ κ΅¬ν˜„
MailService.java μ½”λ“œ 검증 둜직 λ‹¨μˆœν™”
RedisService.java 이메일 검증 둜직 κ°œμ„ 
LoginTest.java JWTProperties λͺ¨μ˜ μΆ”κ°€ 및 토큰 생성 검증 둜직 μˆ˜μ •

Sequence Diagram

sequenceDiagram
    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: 둜그인 응닡
Loading

Possibly related PRs

Suggested labels

βœ… Test

Poem

🐰 ν† ν°μ˜ μΆ€, λ³΄μ•ˆμ˜ λ…Έλž˜
μΏ ν‚€ 속 λΉ„λ°€ 두 개의 λ‚ κ°œ
μ•‘μ„ΈμŠ€μ™€ λ¦¬ν”„λ ˆμ‹œ, 좀을 μΆ”λ„€
μ•ˆμ „ν•œ 문을 ν™œμ§ μ—΄μ–΄μš”
λž˜λΉ—μ˜ λ³΄μ•ˆ λ§ˆλ²• πŸ”’βœ¨


πŸ“œ Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between f16eaec and c25e10d.

πŸ“’ 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: 토큰 μ„€μ •μ˜ 쀑앙화가 잘 κ΅¬ν˜„λ˜μ—ˆμŠ΅λ‹ˆλ‹€!

JWTPropertiesλ₯Ό ν†΅ν•œ 토큰 κ΄€λ ¨ μ„€μ •μ˜ μ€‘μ•™ν™”λŠ” 쒋은 μ ‘κ·Ό λ°©μ‹μž…λ‹ˆλ‹€.

Also applies to: 32-32

Finishing Touches

  • πŸ“ Generate Docstrings (Beta)

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?

❀️ Share
πŸͺ§ Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@ohyuchan123 ohyuchan123 added πŸ”¨ Refactor μ½”λ“œ λ¦¬νŒ©ν† λ§ ✨ Feature κΈ°λŠ₯ 개발 labels Jan 14, 2025
@ohyuchan123 ohyuchan123 changed the title Jwt refresh Jwt Refresh Jan 14, 2025
Copy link

@coderabbitai coderabbitai bot left a 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을 λ°©μ§€ν•˜κ³  더 μ•ˆμ •μ μΈ μ½”λ“œλ₯Ό λ§Œλ“­λ‹ˆλ‹€.

ν•˜μ§€λ§Œ λͺ‡ κ°€μ§€ μ œμ•ˆμ‚¬ν•­μ΄ μžˆμŠ΅λ‹ˆλ‹€:

  1. λ©”μ„œλ“œμ— JavaDoc 주석을 λ³΄κ°•ν•˜μ—¬ null 처리 λ™μž‘μ„ λ¬Έμ„œν™”ν•˜λ©΄ 쒋을 것 κ°™μŠ΅λ‹ˆλ‹€.
  2. μƒμˆ˜λ‘œ "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 μ„€μ •μœΌλ‘œ λ―Όκ°ν•œ λ°μ΄ν„°μ˜ 수λͺ… 관리
  • 이메일 인증 및 속도 μ œν•œ λ©”μ»€λ‹ˆμ¦˜

μΆ”κ°€ λ³΄μ•ˆ κ°•ν™”λ₯Ό μœ„ν•œ μ œμ•ˆ:

  1. ν‚€ 접두사λ₯Ό ν™˜κ²½ λ³€μˆ˜λ‚˜ μ„€μ • 파일둜 λΆ„λ¦¬ν•˜λŠ” 것을 κ³ λ €ν•΄λ³΄μ„Έμš”.
  2. 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

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 5ef4d93 and 49b9c5f.

πŸ“’ 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: μΏ ν‚€ 이름 변경이 μ˜¬λ°”λ₯΄κ²Œ κ΅¬ν˜„λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

μ•‘μ„ΈμŠ€ 토큰을 μœ„ν•œ μΏ ν‚€ 이름이 λͺ…ν™•ν•˜κ²Œ κ΅¬λΆ„λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

λ¦¬ν”„λ ˆμ‹œ 토큰 처리 둜직 μΆ”κ°€ κ²€ν† κ°€ ν•„μš”ν•©λ‹ˆλ‹€.

ν˜„μž¬ ν•„ν„°λŠ” μ•‘μ„ΈμŠ€ ν† ν°λ§Œ μ²˜λ¦¬ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. λ¦¬ν”„λ ˆμ‹œ 토큰 κ°±μ‹  λ‘œμ§μ„ μœ„ν•œ λ³„λ„μ˜ μ—”λ“œν¬μΈνŠΈλ‚˜ ν•„ν„° κ΅¬ν˜„μ„ κ³ λ €ν•΄λ³΄μ„Έμš”.

λ‹€μŒ 슀크립트둜 λ¦¬ν”„λ ˆμ‹œ 토큰 κ΄€λ ¨ μ—”λ“œν¬μΈνŠΈ κ΅¬ν˜„ μ—¬λΆ€λ₯Ό 확인할 수 μžˆμŠ΅λ‹ˆλ‹€:

Comment on lines +93 to +94
String savedCode = redisService.getCode(email); // Redisμ—μ„œ μ½”λ“œλ₯Ό κ°€μ Έμ˜΄

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
String savedCode = redisService.getCode(email); // Redisμ—μ„œ μ½”λ“œλ₯Ό κ°€μ Έμ˜΄
String savedCode = redisService.getCode(email); // Redisμ—μ„œ μ½”λ“œλ₯Ό κ°€μ Έμ˜΄
if (savedCode == null) {
throw new BaseException("λ§Œλ£Œλ˜μ—ˆκ±°λ‚˜ μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” 인증 μ½”λ“œμž…λ‹ˆλ‹€.", HttpStatus.BAD_REQUEST);
}

Comment on lines +13 to +16
private long accessTokenValidityInSeconds;
private long refreshTokenValidityInSeconds;
private String accessTokenCookieName;
private String refreshTokenCookieName;
Copy link

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.

Suggested change
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;
}

Comment on lines 17 to 22
Cookie cookie = new Cookie(jwtProperties.getAccessTokenCookieName(), token);
cookie.setHttpOnly(true);
cookie.setSecure(jwtProperties.isSecureCookie());
cookie.setPath("/");
cookie.setMaxAge((int) jwtProperties.getAccessTokenValidityInSeconds());
return cookie;
Copy link

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

Comment on lines 42 to 48
Cookie cookie = new Cookie(jwtProperties.getAccessTokenCookieName(), null);
cookie.setHttpOnly(true);
cookie.setSecure(true);
cookie.setPath("/");
cookie.setMaxAge(0); // μ¦‰μ‹œ 만료
return cookie;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion

⚠️ Potential issue

만료된 μΏ ν‚€ 생성 둜직 쀑볡 제거 및 λ³΄μ•ˆ μ„€μ • κ°•ν™” ν•„μš”

만료된 μΏ ν‚€ 생성 λ‘œμ§λ„ μ€‘λ³΅λ˜μ–΄ 있으며, 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

Comment on lines 31 to 33
public String createAccessToken(String email) {
return createToken(email, jwtProperties.getAccessTokenValidityInSeconds());
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

토큰 νƒ€μž… ꡬ뢄을 μœ„ν•œ 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);
Copy link

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

Copy link

@coderabbitai coderabbitai bot left a 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 λ¦¬ν”„λ ˆμ‹œ 토큰 λ©”μ»€λ‹ˆμ¦˜μ— λŒ€ν•΄ λ‹€μŒκ³Ό 같은 κ°œμ„ μ‚¬ν•­μ„ μ œμ•ˆλ“œλ¦½λ‹ˆλ‹€:

  1. λ¦¬ν”„λ ˆμ‹œ 토큰 μ €μž₯μ†Œ κ΅¬ν˜„

    • λ°œκΈ‰λœ λ¦¬ν”„λ ˆμ‹œ 토큰을 λ°μ΄ν„°λ² μ΄μŠ€λ‚˜ Redis에 μ €μž₯ν•˜μ—¬ 좔적
    • 토큰 μž¬μ‚¬μš© 탐지 및 차단 κ°€λŠ₯
  2. 토큰 νšŒμ „(Rotation) μ •μ±… λ„μž…

    • λ¦¬ν”„λ ˆμ‹œ 토큰 μ‚¬μš© μ‹œ μƒˆλ‘œμš΄ λ¦¬ν”„λ ˆμ‹œ 토큰 λ°œκΈ‰
    • 이전 λ¦¬ν”„λ ˆμ‹œ 토큰 λ¬΄νš¨ν™”
  3. 토큰 μ·¨μ†Œ(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

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 49b9c5f and 230cb35.

πŸ“’ 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: 토큰 만료 μ‹œκ°„ 검증이 ν•„μš”ν•©λ‹ˆλ‹€.

λ¦¬ν”„λ ˆμ‹œ 토큰과 μ•‘μ„ΈμŠ€ ν† ν°μ˜ κ΅¬ν˜„μ΄ 잘 λ˜μ–΄μžˆμœΌλ‚˜, λ³΄μ•ˆμ„ μœ„ν•΄ λ‹€μŒ 사항듀을 ν™•μΈν•΄μ£Όμ„Έμš”:

  1. λ¦¬ν”„λ ˆμ‹œ ν† ν°μ˜ 유효 기간이 μ•‘μ„ΈμŠ€ 토큰보닀 길게 μ„€μ •λ˜μ–΄ μžˆλŠ”μ§€ 확인
  2. 토큰 만료 μ‹œκ°„μ΄ λ³΄μ•ˆ 정책에 λΆ€ν•©ν•˜λŠ”μ§€ κ²€ν† 
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 μ²˜λ¦¬μ™€ ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ° μŠ€νƒ€μΌμ΄ 잘 μ μš©λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

Comment on lines 23 to 30
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;
}
Copy link

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.

Suggested change
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;
}

Comment on lines +109 to +115
* 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()));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion

λ‘œκ·Έμ•„μ›ƒ λ³΄μ•ˆ κ°•ν™”κ°€ ν•„μš”ν•©λ‹ˆλ‹€.

토큰 λ¬΄νš¨ν™” κ΅¬ν˜„μ΄ 잘 λ˜μ–΄μžˆμœΌλ‚˜, λ‹€μŒκ³Ό 같은 λ³΄μ•ˆ κ°œμ„ μ‚¬ν•­μ„ κ³ λ €ν•΄μ£Όμ„Έμš”:

  1. μΏ ν‚€ 속성 검증 (Secure, HttpOnly, SameSite)
  2. μ„œλ²„ μΈ‘ 토큰 λΈ”λž™λ¦¬μŠ€νŠΈ 관리 μΆ”κ°€
 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.

Comment on lines 56 to 64
// JWT 토큰 생성
String accessToken = jwtUtil.createToken(
member.getEmail(),
jwtProperties.getAccessTokenValidityInSeconds()
);
String refreshToken = jwtUtil.createToken(
member.getEmail(),
jwtProperties.getRefreshTokenValidityInSeconds()
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion

토큰 λ³΄μ•ˆ κ°•ν™”κ°€ ν•„μš”ν•©λ‹ˆλ‹€.

ν˜„μž¬ μ•‘μ„ΈμŠ€ 토큰과 λ¦¬ν”„λ ˆμ‹œ 토큰이 λ™μΌν•œ ν΄λ ˆμž„μ„ μ‚¬μš©ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. λ³΄μ•ˆ κ°•ν™”λ₯Ό μœ„ν•΄ λ‹€μŒ 사항듀을 κ³ λ €ν•΄μ£Όμ„Έμš”:

  1. λ¦¬ν”„λ ˆμ‹œ ν† ν°μ—λŠ” μ΅œμ†Œν•œμ˜ ν΄λ ˆμž„λ§Œ 포함
  2. 토큰 νƒ€μž…μ„ κ΅¬λΆ„ν•˜λŠ” ν΄λ ˆμž„ μΆ”κ°€
  3. λ¦¬ν”„λ ˆμ‹œ ν† ν°μ˜ μ‹λ³„μžλ₯Ό λ°μ΄ν„°λ² μ΄μŠ€μ— μ €μž₯ν•˜μ—¬ 좔적
 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.

Comment on lines +67 to +70
Cookie accessTokenCookie = cookieUtil.createAuthCookie(accessToken); //μ•‘μ„ΈμŠ€ 토큰 μΏ ν‚€
Cookie refreshTokenCookie = cookieUtil.createRefreshCookie(refreshToken); //λ¦¬ν”„λ ˆμ‹œ 토큰 μΏ ν‚€
response.addCookie(accessTokenCookie);
response.addCookie(refreshTokenCookie);
Copy link

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

ꢌμž₯ 사항:

  1. SameSite=Strict λ˜λŠ” Lax 속성 μΆ”κ°€
  2. μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ μ‹€μ œ μš”κ΅¬μ‚¬ν•­μ— 맞게 Path λ²”μœ„ μΆ•μ†Œ
  3. 운영 ν™˜κ²½μ—μ„œ 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

Comment on lines 27 to 49
@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("μœ νš¨ν•˜μ§€ μ•Šμ€ λ¦¬ν”„λ ˆμ‹œ ν† ν°μž…λ‹ˆλ‹€. λ‹€μ‹œ λ‘œκ·ΈμΈν•΄μ£Όμ„Έμš”.");
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

λ³΄μ•ˆ κ°œμ„ μ΄ ν•„μš”ν•©λ‹ˆλ‹€.

λ‹€μŒ λ³΄μ•ˆ κ΄€λ ¨ 사항듀을 κ³ λ €ν•΄μ£Όμ„Έμš”:

  1. CSRF λ³΄ν˜Έκ°€ ν•„μš”ν•©λ‹ˆλ‹€. @CrossOrigin μ„€μ •μ΄λ‚˜ CSRF 토큰 검증을 μΆ”κ°€ν•˜μ„Έμš”.
  2. 토큰 μž¬λ°œκΈ‰ μ—”λ“œν¬μΈνŠΈμ— λŒ€ν•œ rate limiting이 ν•„μš”ν•©λ‹ˆλ‹€.
  3. 토큰 μž¬λ°œκΈ‰ μ‹œλ„ μ‹€νŒ¨μ— λŒ€ν•œ 감사(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.

Comment on lines 85 to 106
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;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion

토큰 검증 둜직 κ°•ν™”κ°€ ν•„μš”ν•©λ‹ˆλ‹€.

ν˜„μž¬ κ΅¬ν˜„μ€ 기본적인 κ²€μ¦λ§Œ μˆ˜ν–‰ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. λ‹€μŒ 검증 ν•­λͺ©λ“€μ„ μΆ”κ°€ν•˜λŠ” 것을 ꢌμž₯ν•©λ‹ˆλ‹€:

  1. 토큰 νƒ€μž… 검증
  2. NBF (Not Before) ν΄λ ˆμž„ 검증
  3. 토큰 λΈ”λž™λ¦¬μŠ€νŠΈ 확인

λ‹€μŒκ³Ό 같이 μˆ˜μ •ν•˜λŠ” 것을 μ œμ•ˆν•©λ‹ˆλ‹€:

     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.

Copy link

@coderabbitai coderabbitai bot left a 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

λ‘œκ·Έμ•„μ›ƒ λ³΄μ•ˆ κ°•ν™”κ°€ ν•„μš”ν•©λ‹ˆλ‹€.

이전 λ¦¬λ·°μ—μ„œ 제기된 λ³΄μ•ˆ λ¬Έμ œκ°€ 아직 ν•΄κ²°λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. ν˜„μž¬ κ΅¬ν˜„μ€ ν΄λΌμ΄μ–ΈνŠΈ μΈ‘ μΏ ν‚€λ§Œ λ¬΄νš¨ν™”ν•˜κ³  μžˆμ–΄, νƒˆμ·¨λœ ν† ν°μ˜ μ•…μš© κ°€λŠ₯성이 μžˆμŠ΅λ‹ˆλ‹€.

λ‹€μŒ λ³΄μ•ˆ κ°œμ„ μ‚¬ν•­μ„ λ°˜λ“œμ‹œ κ΅¬ν˜„ν•΄μ£Όμ„Έμš”:

  1. μ„œλ²„ μΈ‘ 토큰 λΈ”λž™λ¦¬μŠ€νŠΈ 관리
  2. μΏ ν‚€ λ³΄μ•ˆ 속성 μ„€μ • (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: μΏ ν‚€ λ³΄μ•ˆ μ„€μ • κ°œμ„  ν•„μš”

λ³΄μ•ˆμ„± κ°•ν™”λ₯Ό μœ„ν•΄ λ‹€μŒ 사항듀을 κ³ λ €ν•΄μ£Όμ„Έμš”:

  1. μΏ ν‚€μ˜ 도메인을 λͺ…μ‹œμ μœΌλ‘œ μ„€μ •ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.
  2. ν”„λ‘œλ•μ…˜ ν™˜κ²½μ—μ„œλŠ” 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 λ¦¬ν”„λ ˆμ‹œ κ΅¬ν˜„μ„ μœ„ν•œ μΆ”κ°€ κΈ°λŠ₯이 ν•„μš”ν•©λ‹ˆλ‹€.

ν˜„μž¬ κ΅¬ν˜„μ—μ„œ λ‹€μŒκ³Ό 같은 μ€‘μš”ν•œ κΈ°λŠ₯듀이 λˆ„λ½λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€:

  1. 토큰 λ¦¬ν”„λ ˆμ‹œ μ—”λ“œν¬μΈνŠΈ
  2. 토큰 만료 확인 및 처리
  3. λ¦¬ν”„λ ˆμ‹œ 토큰 μž¬μ‚¬μš© 감지

λ‹€μŒκ³Ό 같은 λ©”μ†Œλ“œλ“€μ˜ μΆ”κ°€ κ΅¬ν˜„μ„ μ œμ•ˆλ“œλ¦½λ‹ˆλ‹€:

/**
 * μ•‘μ„ΈμŠ€ 토큰 κ°±μ‹ 
 * μœ νš¨ν•œ λ¦¬ν”„λ ˆμ‹œ 토큰을 μ‚¬μš©ν•˜μ—¬ μƒˆλ‘œμš΄ μ•‘μ„ΈμŠ€ 토큰 λ°œκΈ‰
 */
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

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 230cb35 and 608a2e9.

πŸ“’ 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κ°€ λ„ˆλ¬΄ κ΄‘λ²”μœ„ν•˜κ²Œ 섀정됨

λ‹€μŒ λ³΄μ•ˆ 섀정을 μΆ”κ°€ν•΄μ£Όμ„Έμš”:

  1. SameSite=Strict μ„€μ •
  2. Path λ²”μœ„ μ΅œμ†Œν™”
  3. Secure ν”Œλž˜κ·Έ ν™œμ„±ν™”

56-66: ⚠️ Potential issue

토큰 생성 둜직 κ°œμ„ μ΄ ν•„μš”ν•©λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같은 κ°œμ„ μ‚¬ν•­μ„ μ œμ•ˆλ“œλ¦½λ‹ˆλ‹€:

  1. 토큰 νƒ€μž…μ„ λ¬Έμžμ—΄ λ¦¬ν„°λŸ΄ λŒ€μ‹  μƒμˆ˜λ‚˜ enum으둜 μ •μ˜
  2. μ•‘μ„ΈμŠ€ 토큰과 λ¦¬ν”„λ ˆμ‹œ ν† ν°μ˜ ν΄λ ˆμž„μ„ λΆ„λ¦¬ν•˜μ—¬ λ³΄μ•ˆ κ°•ν™”

λ‹€μŒκ³Ό 같이 μˆ˜μ •ν•΄μ£Όμ„Έμš”:

+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

λ³΄μ•ˆ κ°•ν™” ν•„μš”

λ‹€μŒ λ³΄μ•ˆ κΈ°λŠ₯듀을 μΆ”κ°€ν•΄μ•Ό ν•©λ‹ˆλ‹€:

  1. CSRF 보호
  2. Rate limiting
  3. μ‹€νŒ¨ν•œ 토큰 κ°±μ‹  μ‹œλ„μ— λŒ€ν•œ 감사 λ‘œκΉ…
+@Slf4j
+@RateLimiter(name = "refreshToken")
 @PostMapping("/refresh")
 public TokenResponse refreshAccessToken(...)

Likely invalid or redundant comment.

Comment on lines +49 to +63
/**
* 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()
);
}
Copy link

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

Comment on lines 51 to 61
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);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

토큰 생성 λ‘œμ§μ— μ€‘μš”ν•œ λ³΄μ•ˆ λ¬Έμ œκ°€ μžˆμŠ΅λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같은 λ¬Έμ œμ λ“€μ΄ λ°œκ²¬λ˜μ—ˆμŠ΅λ‹ˆλ‹€:

  1. λ¦¬ν”„λ ˆμ‹œ ν† ν°μ˜ νƒ€μž…μ΄ 잘λͺ» μ„€μ •λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€ ("ACCESS_TOKEN" λŒ€μ‹  "REFRESH_TOKEN" μ‚¬μš© ν•„μš”)
  2. 토큰 생성 μˆœμ„œκ°€ λ³΄μ•ˆμ— μ·¨μ•½ν•  수 μžˆμŠ΅λ‹ˆλ‹€ (μ•‘μ„ΈμŠ€ 토큰을 λ¨Όμ € μƒμ„±ν•˜λŠ” 것이 ꢌμž₯됨)

λ‹€μŒκ³Ό 같이 μˆ˜μ •ν•΄μ£Όμ„Έμš”:

-        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.

Suggested change
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);

Comment on lines +41 to +47
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));

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codebase verification

토큰 관리 λ³΄μ•ˆ 취약점 확인됨

ν˜„μž¬ κ΅¬ν˜„μ—μ„œ λ‹€μŒκ³Ό 같은 λ³΄μ•ˆ 취약점이 λ°œκ²¬λ˜μ—ˆμŠ΅λ‹ˆλ‹€:

  • 이전 λ¦¬ν”„λ ˆμ‹œ 토큰이 λ¬΄νš¨ν™”λ˜μ§€ μ•Šμ•„ μž¬μ‚¬μš© κ°€λŠ₯
  • Redisκ°€ κ΅¬μ„±λ˜μ–΄ μžˆμœΌλ‚˜ 토큰 관리에 ν™œμš©λ˜μ§€ μ•ŠμŒ
  • λ™μ‹œ 토큰 κ°±μ‹  μš”μ²­μ— λŒ€ν•œ 처리 둜직 λΆ€μž¬

ꢌμž₯ 사항:

  1. Redisλ₯Ό ν™œμš©ν•œ 토큰 λΈ”λž™λ¦¬μŠ€νŠΈ κ΅¬ν˜„
  2. λ¦¬ν”„λ ˆμ‹œ 토큰 μž¬λ°œκΈ‰ μ‹œ 이전 토큰 μ¦‰μ‹œ λ¬΄νš¨ν™”
  3. 토큰 λ°œκΈ‰ 이λ ₯ 좔적을 μœ„ν•œ μ €μž₯μ†Œ κ΅¬ν˜„
  4. λ™μ‹œμ„± μ œμ–΄λ₯Ό μœ„ν•œ 락(Lock) λ©”μ»€λ‹ˆμ¦˜ λ„μž…
πŸ”— Analysis chain

토큰 μž¬λ°œκΈ‰ 둜직 검증 ν•„μš”

μƒˆλ‘œμš΄ 토큰 λ°œκΈ‰ μ‹œ λ‹€μŒ 사항듀을 확인해야 ν•©λ‹ˆλ‹€:

  1. 이전 λ¦¬ν”„λ ˆμ‹œ 토큰 λ¬΄νš¨ν™”
  2. 토큰 λ°œκΈ‰ 이λ ₯ 좔적
  3. λ™μ‹œ 토큰 κ°±μ‹  μš”μ²­ 처리
🏁 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

Comment on lines +39 to +41
@Mock
private JWTProperties jwtProperties;

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion

λ¦¬ν”„λ ˆμ‹œ 토큰 ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€ 보완 ν•„μš”

λ‹€μŒ ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λ“€μ„ μΆ”κ°€ν•΄μ•Ό ν•©λ‹ˆλ‹€:

  1. λ¦¬ν”„λ ˆμ‹œ 토큰 만료 μ‹œλ‚˜λ¦¬μ˜€
  2. λ¦¬ν”„λ ˆμ‹œ 토큰을 ν†΅ν•œ μ•‘μ„ΈμŠ€ 토큰 κ°±μ‹ 
  3. 잘λͺ»λœ λ¦¬ν”„λ ˆμ‹œ 토큰 처리
  4. 토큰 νƒ€μž… 검증
@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

Comment on lines +61 to +77
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);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion

토큰 생성 λ³΄μ•ˆ κ°•ν™” ν•„μš”

토큰 생성 μ‹œ λ‹€μŒ λ³΄μ•ˆ κΈ°λŠ₯듀을 μΆ”κ°€ν•΄μ•Ό ν•©λ‹ˆλ‹€:

  1. 토큰 ID (jti) ν΄λ ˆμž„ μΆ”κ°€
  2. Not Before (nbf) ν΄λ ˆμž„ μΆ”κ°€
  3. 토큰 μš©λ„λ³„ 청쀑(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.

Suggested change
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);
}

Comment on lines +87 to 114
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;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codebase verification

토큰 λ³΄μ•ˆ κ°•ν™” ꢌμž₯사항 확인

리뷰 μ˜κ²¬μ—μ„œ μ œμ‹œλœ λ³΄μ•ˆ κ°•ν™” ν•­λͺ©λ“€μ΄ ν˜„μž¬ κ΅¬ν˜„λ˜μ–΄ μžˆμ§€ μ•ŠμŒμ„ ν™•μΈν–ˆμŠ΅λ‹ˆλ‹€. λ‹€μŒ μ‚¬ν•­λ“€μ˜ κ΅¬ν˜„μ„ ꢌμž₯λ“œλ¦½λ‹ˆλ‹€:

  1. Redisλ₯Ό ν™œμš©ν•œ 토큰 λΈ”λž™λ¦¬μŠ€νŠΈ κ΅¬ν˜„

    • κΈ°μ‘΄ RedisService에 토큰 λ¬΄νš¨ν™” κΈ°λŠ₯ μΆ”κ°€
    • λ‘œκ·Έμ•„μ›ƒ μ‹œ 토큰 λΈ”λž™λ¦¬μŠ€νŠΈ 등둝
  2. NBF (Not Before) ν΄λ ˆμž„ μΆ”κ°€

    • JWTUtil의 토큰 생성 μ‹œ .notBefore() μ„€μ •
    • validateToken() λ©”μ†Œλ“œμ—μ„œ μžλ™ 검증됨
  3. 토큰 ID (jti) ν΄λ ˆμž„ μΆ”κ°€ 및 좔적

    • 토큰 생성 μ‹œ 고유 ID λΆ€μ—¬
    • Redisλ₯Ό ν™œμš©ν•΄ μ‚¬μš©λœ 토큰 ID 좔적
πŸ”— Analysis chain

토큰 검증 둜직 κ°•ν™” ν•„μš”

λ‹€μŒ 검증 ν•­λͺ©λ“€μ„ μΆ”κ°€ν•΄μ•Ό ν•©λ‹ˆλ‹€:

  1. 토큰 λΈ”λž™λ¦¬μŠ€νŠΈ 확인
  2. NBF (Not Before) ν΄λ ˆμž„ 검증
  3. 토큰 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

Copy link

@coderabbitai coderabbitai bot left a 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

λ‘œκ·Έμ•„μ›ƒ λ³΄μ•ˆ κ°•ν™”κ°€ ν•„μš”ν•©λ‹ˆλ‹€.

토큰 λ¬΄νš¨ν™” κ΅¬ν˜„μ΄ 잘 λ˜μ–΄μžˆμœΌλ‚˜, λ‹€μŒκ³Ό 같은 λ³΄μ•ˆ κ°œμ„ μ‚¬ν•­μ„ κ³ λ €ν•΄μ£Όμ„Έμš”:

  1. μΏ ν‚€ 속성 검증 (Secure, HttpOnly, SameSite)
  2. μ„œλ²„ μΈ‘ 토큰 λΈ”λž™λ¦¬μŠ€νŠΈ 관리 μΆ”κ°€
 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

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between d2e5305 and f16eaec.

πŸ“’ 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

Comment on lines 51 to 65
// // λ¨Όμ € μ•‘μ„ΈμŠ€ 토큰 생성
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);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

μ‹¬κ°ν•œ 토큰 생성 둜직 였λ₯˜κ°€ λ°œκ²¬λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같은 치λͺ…적인 λ¬Έμ œμ λ“€μ΄ μžˆμŠ΅λ‹ˆλ‹€:

  1. μ•‘μ„ΈμŠ€ 토큰이 λ¦¬ν”„λ ˆμ‹œ ν† ν°μ˜ 유효 기간을 μ‚¬μš©ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€
  2. λ¦¬ν”„λ ˆμ‹œ 토큰이 μ•‘μ„ΈμŠ€ ν† ν°μ˜ 유효 기간을 μ‚¬μš©ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€
  3. μΏ ν‚€ 생성 μ‹œ 토큰이 잘λͺ» μ „λ‹¬λ˜κ³  μžˆμŠ΅λ‹ˆλ‹€
  4. 토큰 νƒ€μž…μ΄ 잘λͺ» μ§€μ •λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€

λ‹€μŒκ³Ό 같이 μˆ˜μ •ν•΄μ£Όμ„Έμš”:

-        // λ¨Όμ € μ•‘μ„ΈμŠ€ 토큰 생성
-        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.

Suggested change
// // λ¨Όμ € μ•‘μ„ΈμŠ€ 토큰 생성
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

λ¦¬ν”„λ ˆμ‹œ 토큰 λ³΄μ•ˆ 취약점이 ν™•μΈλ˜μ—ˆμŠ΅λ‹ˆλ‹€.

ν˜„μž¬ κ΅¬ν˜„μ—μ„œ λ‹€μŒκ³Ό 같은 λ³΄μ•ˆ 취약점이 λ°œκ²¬λ˜μ—ˆμŠ΅λ‹ˆλ‹€:

  1. λ¦¬ν”„λ ˆμ‹œ 토큰 μž¬μ‚¬μš© 감지 λ©”μ»€λ‹ˆμ¦˜μ΄ μ—†μŠ΅λ‹ˆλ‹€.

    • νƒˆμ·¨λœ λ¦¬ν”„λ ˆμ‹œ ν† ν°μ˜ μ•…μš©μ„ λ°©μ§€ν•  수 μ—†μŠ΅λ‹ˆλ‹€.
  2. 토큰 ꡐ체(Rotation) μ‹œ 이전 ν† ν°μ˜ λ¬΄νš¨ν™”κ°€ 이루어지지 μ•ŠμŠ΅λ‹ˆλ‹€.

    • Member μ—”ν‹°ν‹°μ˜ updateRefreshTokenλ§ŒμœΌλ‘œλŠ” μΆ©λΆ„ν•œ λ³΄μ•ˆμ„ μ œκ³΅ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
  3. λ¦¬ν”„λ ˆμ‹œ 토큰 λΈ”λž™λ¦¬μŠ€νŠΈλ‚˜ 토큰 계보 좔적이 κ΅¬ν˜„λ˜μ–΄ μžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

    • /refresh μ—”λ“œν¬μΈνŠΈμ—μ„œ μƒˆλ‘œμš΄ 토큰 λ°œκΈ‰ μ‹œ 이전 ν† ν°μ˜ 좔적이 λΆˆκ°€λŠ₯ν•©λ‹ˆλ‹€.

λ‹€μŒ λ³΄μ•ˆ κΈ°λŠ₯의 μΆ”κ°€λ₯Ό ꢌμž₯λ“œλ¦½λ‹ˆλ‹€:

  • λ¦¬ν”„λ ˆμ‹œ 토큰 μ‚¬μš© μ‹œ 이전 토큰 μ¦‰μ‹œ λ¬΄νš¨ν™”
  • 토큰 계보(family) 좔적을 ν†΅ν•œ μž¬μ‚¬μš© 감지
  • λ¦¬ν”„λ ˆμ‹œ 토큰 λΈ”λž™λ¦¬μŠ€νŠΈ 관리
πŸ”— Analysis chain

λ¦¬ν”„λ ˆμ‹œ 토큰 λ³΄μ•ˆ κ°•ν™”κ°€ ν•„μš”ν•©λ‹ˆλ‹€.

λ¦¬ν”„λ ˆμ‹œ ν† ν°μ˜ λ³΄μ•ˆμ„ κ°•ν™”ν•˜κΈ° μœ„ν•΄ λ‹€μŒ κΈ°λŠ₯λ“€μ˜ κ΅¬ν˜„μ„ κ³ λ €ν•΄μ£Όμ„Έμš”:

  1. λ¦¬ν”„λ ˆμ‹œ 토큰 ꡐ체 (Rotation)
  2. λ¦¬ν”„λ ˆμ‹œ 토큰 μž¬μ‚¬μš© 감지
  3. λ¦¬ν”„λ ˆμ‹œ 토큰 만료 μ‹œκ°„ 검증

λ¦¬ν”„λ ˆμ‹œ 토큰 κ΅¬ν˜„μ΄ μ μ ˆν•œμ§€ ν™•μΈν•˜κΈ° μœ„ν•΄ λ‹€μŒ 검증을 μ‹€ν–‰ν•©λ‹ˆλ‹€:

🏁 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

@ohyuchan123 ohyuchan123 merged commit e009402 into develop Jan 15, 2025
2 checks passed
@ohyuchan123 ohyuchan123 changed the title Jwt Refresh πŸ”€ Jwt Refresh Jan 15, 2025
@ohyuchan123 ohyuchan123 self-assigned this Jan 15, 2025
@coderabbitai coderabbitai bot mentioned this pull request Jan 15, 2025
@ohyuchan123 ohyuchan123 deleted the refactor/jwt-refresh branch January 15, 2025 09:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature κΈ°λŠ₯ 개발 πŸ”¨ Refactor μ½”λ“œ λ¦¬νŒ©ν† λ§

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants