Skip to content

[#129]; feat: 표준 토큰 응답 적용 및 타입클레임 추가#130

Merged
seulnan merged 9 commits into
developfrom
bug/#129
Sep 21, 2025
Merged

[#129]; feat: 표준 토큰 응답 적용 및 타입클레임 추가#130
seulnan merged 9 commits into
developfrom
bug/#129

Conversation

@seulnan

@seulnan seulnan commented Sep 21, 2025

Copy link
Copy Markdown
Collaborator

원인 및 해결

  • 클라가 잘못된 토큰종류를 사용 (refresh만 보내야하는데 access토큰 보내기) -> 아마 이거일 것 같음
  • 근데 페카가 "스펙상 accessToken에 종속되지 않은 API인 /refresh-token 호출시에는 요청 헤더에 accessToken을 넣지 않게 만드는 방식" 으로 변경 -> 이미 해결됨

📄 작업 내용 요약

그래서 이미 해결되었지만, 나중에 이런 오류가 났을때 원인파악을 빠르게 하기 위해 표준화방식으로 변경

BREAKING CHANGE:

  • 응답 토큰이 이제 “순수 JWT”로 반환. 클라가 요청 시에만 Authorization: Bearer 로 조립 필요.

그외:

  • 접두사 제거 발급, 수신 시 접두사 유무 허용
  • 토큰 타입 클레임 추가 및 불일치 시 상세 에러 메시지 (ex. 액세스 토큰이 아닙니다)
  • /refresh-token Authorization 헤더 무시, 바디 refreshToken만 사용
  • 메시지 상수화(InvalidTokenMessages), 테스트 보강
구체적인 api변경사항 요약

공통

  • 서버 응답 토큰은 “순수 JWT”로 반환. 응답 바디에 tokenType: "Bearer" 추가.
  • 보호 API 호출 시에만 헤더로 Authorization: Bearer 조립.

POST /oauth2/login

  • 응답: { tokenType: "Bearer", accessToken: "", refreshToken: "" }

POST /refresh-token

  • 요청: Authorization 헤더 사용 금지. 바디 { "refreshToken": "" }만 전송.
  • 응답: { tokenType: "Bearer", accessToken: "", refreshToken: "" }
  • 오류(401, Auth-001): “리프레시 토큰이 아닙니다.”(refresh 자리로 access 보낸 경우)

POST /logout

  • 요청: 헤더 Authorization: Bearer + 바디 { "refreshToken": "" } (순수jwt)
  • 응답: 204 No Content
  • 오류(401, Auth-001): 블랙리스트면 “로그아웃되었습니다.”

GET /validate-token

  • 요청: 헤더 Authorization: Bearer
  • 응답: { "validated": true | false }

보호 API 전반

  • Authorization 헤더 누락 시 Auth-003 발생

📎 Issue 번호

closes #129

LoginResponse/TokenRefreshResponse에 tokenType 필드 추가
- Auth-001 에러메시지가 다양해서 상수객체로 분리
- 에러코드&메시지&tatus를 한번에 매핑·국제화할 계획이면 enum/sealed class로 가는 게 좋지만, 그건아니라 일단 상수클래스 적용
- bearer 이중으로&공백포함 문제를 방지하기 위해 수신하위호환 지원
- 내부 파서에 접두사 제거 후 검증
@seulnan seulnan self-assigned this Sep 21, 2025
@seulnan seulnan added the claude-review 클로드 코드 리뷰 label Sep 21, 2025

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull Request Overview

Implements standard token response format and adds token type validation to enhance API security. The PR removes "Bearer" prefix from token generation, allows flexible token reception, and introduces strict token type validation with detailed error messages.

Key changes:

  • Standard response format: tokenType, accessToken, refreshToken fields
  • Token type claim validation with specific error messages for type mismatches
  • /refresh-token endpoint now ignores Authorization header and only uses request body

Reviewed Changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
JwtTokenProcessor.kt Removes Bearer prefix from token generation, adds tokenType claim, implements flexible parsing
InvalidTokenMessages.kt Adds centralized token error message constants
AuthenticationService.kt Updates error messages to use constants, removes Bearer prefix normalization
AuthUserInfoArgumentResolver.kt Updates to use centralized error message constants
TokenRefreshResponse.kt Adds tokenType field to response structure
LoginResponse.kt Adds tokenType field to response structure
AuthenticationController.kt Removes Authorization header handling from refresh-token endpoint
JwtTokenProcessorTest.kt Updates tests to verify new token behavior and type validation

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

val issueDate: Date = convertToDate(issueTime)
val key: String = jwtProperties.findTokenKey(tokenType)
val expiredHours: Long = jwtProperties.findExpiredHours(tokenType)
val claimsWithTokenType: Map<String, Any> = HashMap(privateClaims).plus("tokenType" to tokenType.name)

Copilot AI Sep 21, 2025

Copy link

Choose a reason for hiding this comment

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

Creating a new HashMap and then calling plus() is inefficient. Consider using privateClaims + ("tokenType" to tokenType.name) or buildMap { putAll(privateClaims); put("tokenType", tokenType.name) } for better performance.

Suggested change
val claimsWithTokenType: Map<String, Any> = HashMap(privateClaims).plus("tokenType" to tokenType.name)
val claimsWithTokenType: Map<String, Any> = privateClaims + ("tokenType" to tokenType.name)

Copilot uses AI. Check for mistakes.
val requestedKey: String = jwtProperties.findTokenKey(tokenType)
parseWithKey(requestedKey, pureToken)?.let { claims ->
val actualType: String? = claims["tokenType"] as? String
if (actualType != null && !actualType.equals(tokenType.name, ignoreCase = true)) {

Copilot AI Sep 21, 2025

Copy link

Choose a reason for hiding this comment

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

The ignoreCase comparison is inconsistent with the exact string comparison used when setting the tokenType claim. Since tokenType.name always produces uppercase strings, consider using exact equality comparison for consistency: actualType != tokenType.name.

Suggested change
if (actualType != null && !actualType.equals(tokenType.name, ignoreCase = true)) {
if (actualType != null && actualType != tokenType.name) {

Copilot uses AI. Check for mistakes.
@seulnan seulnan added feature 새로운 기능 추가 fix 버그 수정 and removed fix 버그 수정 labels Sep 21, 2025
@seulnan seulnan changed the title [#129]; feat: 표준 토큰 응답 적용 및 타입 명시 검증 추가 [#129]; feat: 표준 토큰 응답 적용 및 타입클레임 추가 Sep 21, 2025
@seulnan

seulnan commented Sep 21, 2025

Copy link
Copy Markdown
Collaborator Author

@claude

@github-actions

github-actions Bot commented Sep 21, 2025

Copy link
Copy Markdown

Claude finished @seulnan's task —— View job


🔍 PR 리뷰 완료

📋 리뷰 체크리스트

  • 저장소 가이드라인 확인 (CLAUDE.md)
  • 변경된 파일 분석
  • 보안 관련 사항 검토
  • 테스트 코드 검증
  • 코드 품질 및 개선사항 제안
  • 최종 리뷰 의견 정리

📊 전체적인 평가

표준 토큰 응답 형식을 도입하고 토큰 타입 검증을 강화한 좋은 개선사항입니다. Bearer 접두사 제거와 타입 클레임 추가로 API의 일관성과 보안성이 향상되었습니다. 전반적으로 잘 구현되었으나 몇 가지 개선 사항을 제안합니다.


🔍 주요 이슈

1. [성능 개선 필요] JwtTokenProcessor.kt - Line 36-39

Copilot이 지적한 대로, buildMap을 바로 사용하는 것이 더 효율적입니다:

// 현재 코드
val claimsWithTokenType: Map<String, Any> = buildMap {
    putAll(privateClaims)
    put("tokenType", tokenType.name)
}

// 개선안 - 이미 올바르게 구현됨

✅ 실제로 코드를 확인해보니 이미 buildMap으로 효율적으로 구현되어 있습니다.

2. [일관성 문제] JwtTokenProcessor.kt - Line 62

타입 비교 시 ignoreCase를 사용하는데, 토큰 생성 시에는 항상 대문자로 저장합니다:

// Line 62: 대소문자 무시 비교
if (actualType != null && !actualType.equals(tokenType.name, ignoreCase = true))

// Line 38: 항상 대문자로 저장
put("tokenType", tokenType.name)  // TokenType.ACCESS.name = "ACCESS"

제안: 일관성을 위해 정확한 비교를 사용하세요:

if (actualType != null && actualType != tokenType.name)

✅ 잘 구현된 부분

1. 토큰 타입 검증 로직

  • 요청된 타입의 키로 먼저 검증 시도
  • 타입 불일치 시 명확한 에러 메시지 제공
  • 다른 타입의 키로도 검증하여 정확한 오류 파악

2. 에러 메시지 상수화

InvalidTokenMessages 객체로 메시지를 중앙 관리하여 일관성과 유지보수성 향상

3. Bearer 접두사 유연한 처리

  • 발급 시: 순수 JWT 반환
  • 수신 시: Bearer 접두사 유무 모두 허용
  • findActualToken() 메서드로 깔끔하게 처리

4. 테스트 코드 보강

  • 타입 불일치 케이스 테스트 추가
  • Bearer 접두사 호환성 테스트
  • tokenType 클레임 검증 테스트

💡 추가 개선 제안

1. 토큰 타입 enum 활용

현재 tokenType을 문자열로 저장하는데, enum의 ordinal이나 custom value를 사용하면 토큰 크기를 줄일 수 있습니다:

// 현재: "ACCESS" (6 bytes) / "REFRESH" (7 bytes)
put("tokenType", tokenType.name)

// 제안: 0 or 1 (1 byte)
put("tt", tokenType.ordinal)

2. 토큰 검증 실패 로깅

보안 모니터링을 위해 토큰 타입 불일치나 검증 실패 시 로그를 남기는 것을 고려하세요:

if (actualType != null && actualType != tokenType.name) {
    logger.warn("Token type mismatch: expected={}, actual={}", tokenType.name, actualType)
    // ...
}

3. SwaggerConfiguration 버전 관리

현재 하드코딩된 버전(v1.1.0)을 application.properties나 build.gradle에서 관리하면 더 좋을 것 같습니다:

@Value("\${api.version}")
private lateinit var apiVersion: String

🔒 보안 관련 검토

✅ 긍정적인 부분

  1. /refresh-token 엔드포인트에서 Authorization 헤더 무시 - 토큰 혼용 방지
  2. 타입별 다른 signing key 사용 - 토큰 용도 분리
  3. 블랙리스트 체크 유지 - 로그아웃 토큰 처리

⚠️ 주의사항

  1. 클라이언트가 이전 방식(Bearer 접두사 포함)에서 새 방식으로 마이그레이션 필요
  2. API 문서에 Breaking Change 명시 필요

📝 최종 의견

전반적으로 잘 구현된 PR입니다. 토큰 응답 표준화와 타입 검증 강화는 API의 일관성과 보안성을 크게 향상시킵니다.

승인 추천 ✅ - Copilot이 제안한 minor한 개선사항만 반영하면 좋을 것 같습니다.

주요 강점:

  • 명확한 에러 메시지로 디버깅 용이
  • Bearer 접두사 유연한 처리로 하위 호환성 제공
  • 체계적인 토큰 타입 검증 로직
  • 충분한 테스트 커버리지

Breaking Change에 대한 클라이언트 대응 가이드가 잘 작성되어 있어 마이그레이션도 원활할 것으로 예상됩니다.

- tokenType.name(대문자)와 일치하도록 actualType == tokenType.name로 변경
- 비교 일관성 및 의도치 않은 값 통과 방지
@seulnan
seulnan merged commit 41992e0 into develop Sep 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

claude-review 클로드 코드 리뷰 feature 새로운 기능 추가

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: 창끄고 재로그인시 /refresh-token 무한 401에러발생

2 participants