Fix: 초당 2번 이상의 요청이 올 경우 같은 토큰 발급되는 오류 수정#45
Conversation
JWT 토큰 jti 랜덤 값 들어가게 수정
WalkthroughJWT 생성 시 토큰 ID를 별도 생성기로 분리하고, AUTH_ID 클레임을 추가했습니다. 토큰 파싱 측은 AUTH_ID를 타입 안전하게 읽도록 변경했습니다. JwtConstants에 AUTH_ID 상수를 추가했고, JwtComponent는 TokenIdGenerator를 의존성으로 주입받습니다. Changes
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 생성기 사용
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로 파싱
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
✨ Finishing touches
🧪 Generate unit tests
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.
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. Comment Pre-merge checks❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
|
There was a problem hiding this comment.
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
📒 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(' 호출을 찾지 못했습니다. 스프링 컨텍스트/테스트에서 빈 주입 실패 우려 없음.
| .id(tokenIdGenerator.generate()) | ||
| .claim(JwtConstants.AUTH_ID, userAuth.authId()) | ||
| .claim(JwtConstants.USER_ID, userAuth.userId()) |
There was a problem hiding this comment.
💡 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.
JWT 토큰 jti 랜덤 값 들어가게 수정
📝 변경 내용
✅ 체크리스트
💬 기타 참고 사항
Summary by CodeRabbit
Refactor
Chores