Skip to content

Fix: 초당 2번 이상의 요청이 올 경우 같은 토큰 발급되는 오류 수정#45

Merged
dungbik merged 1 commit into
developfrom
feat/refresh-token
Sep 15, 2025
Merged

Fix: 초당 2번 이상의 요청이 올 경우 같은 토큰 발급되는 오류 수정#45
dungbik merged 1 commit into
developfrom
feat/refresh-token

Conversation

@dungbik
Copy link
Copy Markdown
Contributor

@dungbik dungbik commented Sep 15, 2025

JWT 토큰 jti 랜덤 값 들어가게 수정

📝 변경 내용


✅ 체크리스트

  • 코드가 정상적으로 동작함
  • 테스트 코드 통과함
  • 문서(README 등)를 최신화함
  • 코드 스타일 가이드 준수

💬 기타 참고 사항

Summary by CodeRabbit

  • Refactor

    • 인증 토큰 구조를 정비해 식별자 생성 방식을 개선하고 추가 식별 정보를 포함했습니다. 이를 통해 로그인 및 세션 유지의 안정성과 추적 가능성이 향상되었습니다.
  • Chores

    • 인증 관련 로깅 확장을 위한 준비 작업을 수행했습니다.
    • 내부 구성 요소를 모듈화하여 향후 유지보수와 확장성을 높였습니다.

@dungbik dungbik self-assigned this Sep 15, 2025
@dungbik dungbik added the bug Something isn't working label Sep 15, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Sep 15, 2025

Walkthrough

JWT 생성 시 토큰 ID를 별도 생성기로 분리하고, AUTH_ID 클레임을 추가했습니다. 토큰 파싱 측은 AUTH_ID를 타입 안전하게 읽도록 변경했습니다. JwtConstants에 AUTH_ID 상수를 추가했고, JwtComponent는 TokenIdGenerator를 의존성으로 주입받습니다.

Changes

Cohort / File(s) Change Summary
JWT 발급 로직 업데이트
src/main/java/project/flipnote/common/security/jwt/JwtComponent.java
생성자에 TokenIdGenerator 주입 추가, @Slf4j 추가. JWT ID를 tokenIdGenerator.generate()로 교체. 새 클레임 AUTH_ID 추가. 기존 클레임 및 서명 흐름 유지.
토큰 ID 생성기 추가
src/main/java/project/flipnote/common/security/jwt/TokenIdGenerator.java
@Component 클래스로 추가. SecureRandom 기반 양수 long을 문자열로 반환하는 generate() 제공.
클레임 상수 확장
src/main/java/project/flipnote/common/security/jwt/JwtConstants.java
public static final String AUTH_ID = "auth_id"; 상수 추가.
클레임 파싱 방식 수정
src/main/java/project/flipnote/common/security/dto/AuthPrinciple.java
from(Claims)에서 authId 추출을 claims.get(JwtConstants.AUTH_ID, Long.class)로 변경. 다른 필드 추출 로직은 동일.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant S as Service (JwtComponent)
  participant G as TokenIdGenerator
  participant J as JWT Builder/Signer

  Client->>S: issueToken(userAuth)
  S->>G: generate()
  G-->>S: tokenId (String)
  S->>J: set id(tokenId), set claims{AUTH_ID, USER_ID, ROLE, TOKEN_VERSION}
  J-->>S: signed JWT
  S-->>Client: JWT
  note over S,J: 새로 추가: AUTH_ID 클레임, 토큰 ID 생성기 사용
Loading
sequenceDiagram
  autonumber
  participant F as Auth Filter
  participant A as AuthPrinciple.from
  participant C as Claims

  F->>A: from(Claims)
  A->>C: get(AUTH_ID, Long.class)
  A->>C: get(USER_ID, Long.class), get(ROLE, String), get(TOKEN_VERSION, Integer)
  A-->>F: AuthPrinciple
  note over A,C: AUTH_ID를 타입 지정 getter로 파싱
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • stoneTiger0912

Poem

새 토큰 싹을 톡, 난 숫자 씨앗을 뿌려요 🎲
ID는 바람처럼, 클레임은 별처럼 빛나죠 ✨
AUTH_ID 챙겨 넣고, 길을 잃지 않게—
보안의 정원에서 깡총깡총,
오늘도 안전을 수확해요, 토큰 토끼가! 🐇🔐

✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/refresh-token

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed PR 제목 "Fix: 초당 2번 이상의 요청이 올 경우 같은 토큰 발급되는 오류 수정"은 변경의 핵심 목표인 초당 다중 요청 시 동일 JWT 발급 문제를 해결한다는 점을 명확하고 간결하게 요약합니다. 제목은 실제 코드 변경(무작위 jti 생성 추가 및 기존 jti 사용 방식 수정)과 직접적으로 연관되어 있으며 불필요한 잡음 없이 주된 의도를 전달합니다. 따라서 팀원이 히스토리를 빠르게 파악하는 데 충분합니다.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

Copy link
Copy Markdown

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/project/flipnote/common/security/dto/AuthPrinciple.java (1)

31-41: 구버전 토큰 호환성 깨짐: AUTH_ID 미존재 시 NPE/500 가능

현재 claims.get(AUTH_ID, Long.class)가 null이면 원시형 long 언박싱에서 즉시 NPE가 납니다. 기존(jti=authId) 토큰을 안전하게 처리하도록 폴백을 추가해 주세요.

권장 수정(diff는 해당 범위 내):

-    long authId = claims.get(JwtConstants.AUTH_ID, Long.class);
-    long userId = claims.get(JwtConstants.USER_ID, Long.class);
+    Long authId = claims.get(JwtConstants.AUTH_ID, Long.class);
+    if (authId == null) {
+      String jti = claims.getId();
+      if (jti == null) {
+        throw new project.flipnote.common.security.exception.CustomSecurityException(
+          project.flipnote.common.security.exception.SecurityErrorCode.NOT_VALID_JWT_TOKEN
+        );
+      }
+      try {
+        authId = Long.parseLong(jti);
+      } catch (NumberFormatException e) {
+        throw new project.flipnote.common.security.exception.CustomSecurityException(
+          project.flipnote.common.security.exception.SecurityErrorCode.NOT_VALID_JWT_TOKEN
+        );
+      }
+    }
+    Long userId = claims.get(JwtConstants.USER_ID, Long.class);

또한 지역 변수 타입을 레코드 정의(Long)와 맞춰 언박싱 NPE를 방지했습니다.

🧹 Nitpick comments (2)
src/main/java/project/flipnote/common/security/jwt/TokenIdGenerator.java (1)

12-15: Math.abs(nextLong())는 Long.MIN_VALUE에서 음수 유지 — jti 양수 보장 실패 가능

희박하지만 음수 jti가 나올 수 있습니다. 최소 수정안 또는 128비트 Base64URL로의 개선을 제안합니다.

최소 수정안(숫자 문자열 유지):

-    long value = Math.abs(random.nextLong());
-    return Long.toString(value);
+    long v = random.nextLong();
+    return Long.toUnsignedString(v);

권장 개선안(충돌 확률/가독성 개선: 128비트 Base64URL, 패딩 제거):

-    long value = Math.abs(random.nextLong());
-    return Long.toString(value);
+    byte[] bytes = new byte[16];
+    random.nextBytes(bytes);
+    return java.util.Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
src/main/java/project/flipnote/common/security/jwt/JwtComponent.java (1)

15-15: @slf4j 미사용

로깅이 없다면 애노테이션 제거, 사용할 계획이면 토큰 생성/검증 실패 지점에 로그 추가를 권장합니다.

Also applies to: 23-23

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f55c3e8 and 50f3a32.

📒 Files selected for processing (4)
  • src/main/java/project/flipnote/common/security/dto/AuthPrinciple.java (1 hunks)
  • src/main/java/project/flipnote/common/security/jwt/JwtComponent.java (2 hunks)
  • src/main/java/project/flipnote/common/security/jwt/JwtConstants.java (1 hunks)
  • src/main/java/project/flipnote/common/security/jwt/TokenIdGenerator.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/project/flipnote/common/security/jwt/JwtComponent.java (1)
src/main/java/project/flipnote/common/security/jwt/TokenIdGenerator.java (1)
  • Component (7-16)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (2)
src/main/java/project/flipnote/common/security/jwt/JwtConstants.java (1)

13-13: AUTH_ID 상수 추가 — 구버전 토큰(jti) 호환성 확인 필요

제공된 rg 스크립트 실행 결과가 출력 없이 종료되어 코드베이스에서 jti(getId) 또는 Jwts.builder().id 사용 흔적을 자동으로 찾지 못했습니다.
구버전 토큰에 auth_id(claim)가 없을 경우 파싱/매핑 시 NPE 위험이 있으니 Auth 매핑(AuthPrincipal 등)에서 null-safe 처리(기본값, Optional 또는 jti fallback)를 적용하세요.

로컬에서 재확인하려면 다음 명령을 실행해 결과를 첨부하세요:

#!/bin/bash
rg -nP -C2 --type=java '\bclaims\s*\.\s*getId\s*\('
rg -nP -C2 --type=java 'Jwts\.builder\(\)\.id\s*\('
src/main/java/project/flipnote/common/security/jwt/JwtComponent.java (1)

30-31: 확인 완료 — TokenIdGenerator 빈 존재 및 JwtComponent 수동 생성 없음

TokenIdGenerator는 src/main/java/project/flipnote/common/security/jwt/TokenIdGenerator.java에 @component로 등록되어 있고, 레포지토리 전체 검색에서 'new JwtComponent(' 호출을 찾지 못했습니다. 스프링 컨텍스트/테스트에서 빈 주입 실패 우려 없음.

Comment on lines +70 to 72
.id(tokenIdGenerator.generate())
.claim(JwtConstants.AUTH_ID, userAuth.authId())
.claim(JwtConstants.USER_ID, userAuth.userId())
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

jti 무작위화 + AUTH_ID 클레임 분리 — 동시요청 동일 토큰 문제 해결

.id(tokenIdGenerator.generate())로 jti를 무작위화하고, AUTH_ID를 별도 클레임으로 추가한 점 적절합니다. 동일 초 단위 중복 발급 이슈를 제거합니다.

운영 호환성 확인(구토큰 유효기간 동안):


🏁 Script executed:

#!/bin/bash
# AUTH_ID 미포함 토큰이 아직 유통 중인지, 토큰 만료 정책/그레이스 기간 문서 확인 필요
# 코드 기준 검증: AUTH_ID 사용처
rg -nP --type=java 'JwtConstants\.AUTH_ID|claims\.get\(JwtConstants\.AUTH_ID'

Length of output: 257


jti 무작위화 + AUTH_ID 클레임 분리 — 구 토큰 호환성(필수 확인/수정)

.id(tokenIdGenerator.generate())와 AUTH_ID 추가는 적절함. 그러나 AuthPrinciple에서 AUTH_ID를 primitive long으로 바로 읽고 있어( src/main/java/project/flipnote/common/security/dto/AuthPrinciple.java:32 ) AUTH_ID가 없는 기존 토큰이 유통 중이면 claims.get(...)이 null로 인해 NPE 또는 토큰 파싱 실패가 발생함. 토큰 생성 위치: src/main/java/project/flipnote/common/security/jwt/JwtComponent.java:70-72.

  • 조치: claims.get(...)을 Long으로 받아 null 체크 후 호환(fallback 처리 또는 안전한 거부 로그) 로직 추가하거나, 배포 전 기존 토큰 만료/그레이스 기간을 확인해 단계적 롤아웃 계획 수립.
🤖 Prompt for AI Agents
In src/main/java/project/flipnote/common/security/jwt/JwtComponent.java around
lines 70-72, you added .id(tokenIdGenerator.generate()) and
.claim(JwtConstants.AUTH_ID, userAuth.authId()), but existing tokens may lack
AUTH_ID and AuthPrinciple currently reads it as a primitive long (causing NPE on
missing claim). Fix by changing claim extraction in AuthPrinciple to read
AUTH_ID as a Long (wrapper), null-check it, and implement a safe fallback (e.g.,
use userId or a sentinel value, or reject with a clear logged message) so
parsing won’t NPE; add a warning log when falling back and verify rollout/expiry
plan for old tokens before enforcing non-null AUTH_ID.

@dungbik dungbik merged commit fea4430 into develop Sep 15, 2025
3 checks passed
@dungbik dungbik deleted the feat/refresh-token branch September 15, 2025 14:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants