Skip to content

Fix/#34 reissue API 500 오류 응답 수정#35

Merged
ojy0903 merged 2 commits intodevelopfrom
fix/#34
Feb 16, 2026
Merged

Fix/#34 reissue API 500 오류 응답 수정#35
ojy0903 merged 2 commits intodevelopfrom
fix/#34

Conversation

@ojy0903
Copy link
Collaborator

@ojy0903 ojy0903 commented Feb 16, 2026

📌 관련 이슈

🚀 개요

이번 PR에서 변경된 핵심 내용을 요약해주세요.

reissue API 관련 오류 수정

📄 작업 내용

구체적인 작업 내용을 설명해주세요.

  • reissue API 에 쿠키 값 누락된 상태로 요청 시 500 오류 현상 수정,
  • reissue API 에 만료된 refreshToken 값으로 요청시 "잘못된 토큰 형식" -> "토큰 만료" 로 수정

📸 스크린샷 / 테스트 결과 (선택)

결과물 확인을 위한 사진이나 테스트 로그를 첨부해주세요.

===기존 오류 응답===

  1. 쿠키 값 없이 reissue 요청 시 500 오류
기존 쿠키 존재X 기존 500오류
  1. 만료된 refreshToken 값으로 reissue 요청 시 오류 응답
기존 refreshToken 만료시

===수정 이후 응답===

  1. 쿠키 값 누락 시
수정 이후 no cookie
  1. refreshToken 만료된 상태에서 요청 시
수정 이후 만료

✅ 체크리스트

  • [✅] 브랜치 전략(GitHub Flow)을 준수했나요?
  • [✅] 메서드 단위로 코드가 잘 쪼개져 있나요?
  • [✅] 테스트 통과 확인
  • [✅] 서버 실행 확인
  • [✅] API 동작 확인

🔍 리뷰 포인트 (Review Points)

리뷰어가 중점적으로 확인했으면 하는 부분을 적어주세요. (P1~P4 적용 가이드)

  • 디버깅 과정에서 refreshToken 이 만료된 상태로 요청 시 응답값도 자세하게 나타내는 게 나을 것 같아 추가 수정 진행했습니다!

💬 리뷰어 가이드 (P-Rules)
P1: 필수 반영 (Critical) - 버그 가능성, 컨벤션 위반. 해결 전 머지 불가.
P2: 적극 권장 (Recommended) - 더 나은 대안 제시. 가급적 반영 권장.
P3: 제안 (Suggestion) - 아이디어 공유. 반영 여부는 드라이버 자율.
P4: 단순 확인/칭찬 (Nit) - 사소한 오타, 칭찬 등 피드백.

Summary by CodeRabbit

릴리스 노트

  • 버그 수정
    • 만료된 토큰 및 누락된 새로고침 토큰에 대한 예외 처리 개선
    • 인증 오류 발생 시 명확한 오류 응답 제공
    • 토큰 관련 요청 실패에 대한 적절한 HTTP 상태 코드 반환

@ojy0903 ojy0903 self-assigned this Feb 16, 2026
@ojy0903 ojy0903 added the 🐛 Bug 버그 수정 label Feb 16, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 16, 2026

Walkthrough

reissue API에서 쿠키 누락 시 발생하는 500 에러를 해결하기 위해 새로운 인증 오류 코드와 전역 예외 핸들러를 추가했습니다. MissingRequestCookieException과 ExpiredJwtException을 처리하여 400대 오류로 응답하도록 개선했습니다.

Changes

Cohort / File(s) Summary
인증 오류 코드 추가
src/main/java/com/whereyouad/WhereYouAd/domains/user/exception/code/AuthErrorCode.java
TOKEN_NOT_FOUND 열거형 상수를 추가하여 쿠키에 refreshToken이 없는 상황을 HTTP 401로 정의했습니다.
전역 예외 핸들러 확장
src/main/java/com/whereyouad/WhereYouAd/global/exception/GlobalExceptionHandler.java
MissingRequestCookieException(쿠키 누락)과 ExpiredJwtException(토큰 만료)을 처리하는 2개의 예외 핸들러 메서드를 추가했습니다. 이제 이 예외들이 발생하면 400/401 오류로 클라이언트에 반환됩니다.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes


리뷰 피드백

잘 짠 부분:

  • 문제 분석이 명확합니다. 쿠키 값이 삭제된 경우에 500 에러가 나는 근본 원인을 정확히 파악하고, 전역 예외 핸들러에서 처리하는 접근법이 좋습니다.
  • TOKEN_NOT_FOUND라는 명확한 에러 코드를 추가하여 클라이언트가 상황을 쉽게 이해할 수 있습니다.
  • 두 개의 관련된 예외(MissingRequestCookieException, ExpiredJwtException)를 함께 처리하여 토큰 관련 모든 오류를 일관성 있게 관리하고 있습니다.

⚠️ 확인 사항:

  • handleMissingCookieException에서 정말로 TOKEN_NOT_FOUND를 반환하는지, handleExpiredJwtException에서 TOKEN_EXPIRED를 반환하는지 한 번 더 확인해주세요. 매핑이 정확해야 클라이언트가 올바른 오류 코드를 받을 수 있습니다.
  • 혹시 다른 토큰 관련 예외(InvalidTokenException, MalformedJwtException 등)가 있다면, 이번 기회에 함께 처리하는 것을 고려해볼 만합니다. 지금은 두 가지만 처리하고 있는데, 이후 유지보수 시 누락될 수 있습니다.
🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Merge Conflict Detection ⚠️ Warning ⚠️ Unable to check for merge conflicts: Invalid branch name format
✅ Passed checks (5 passed)
Check name Status Explanation
Description check ✅ Passed PR 설명이 리포지토리의 템플릿을 잘 따르고 있으며, 핵심 내용, 작업 내용, 테스트 결과, 체크리스트가 모두 충실하게 작성되어 있습니다.
Linked Issues check ✅ Passed 변경 사항이 이슈 #34의 요구사항을 완벽하게 충족하고 있습니다. MissingRequestCookieException과 ExpiredJwtException 처리 추가로 500 오류를 4xx 오류로 변환했으며, 새로운 AuthErrorCode(TOKEN_NOT_FOUND)를 추가했습니다.
Out of Scope Changes check ✅ Passed 모든 변경 사항이 이슈 #34의 범위 내에서 이루어졌으며, 불필요한 수정이나 범위 외 기능 추가가 없습니다.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed Pull request title clearly references the issue being fixed (#34) and summarizes the main change: fixing the reissue API 500 error response, which aligns with the changeset's primary objective.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/#34
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch fix/#34
  • Create stacked PR with resolved conflicts
  • Post resolved changes as copyable diffs in a comment

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.

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: 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/com/whereyouad/WhereYouAd/domains/user/exception/code/AuthErrorCode.java (1)

14-19: ⚠️ Potential issue | 🔴 Critical

🐛 에러 코드 "AUTH_401_4" 가 중복됩니다.

TOKEN_NOT_FOUND (Line 14)와 USER_NOT_FOUND (Line 19) 모두 "AUTH_401_4"를 사용하고 있어요. 클라이언트 측에서 에러 코드로 분기 처리를 할 때 두 에러를 구분할 수 없게 됩니다.

예를 들어, 프론트엔드에서 code === "AUTH_401_4" 로 분기하면 "refreshToken 없음"인지 "회원 없음"인지 알 수 없겠죠?

추가로, USER_NOT_FOUNDHttpStatus.NOT_FOUND (404)를 사용하면서 코드는 AUTH_401_4 (401 계열 네이밍)로 되어 있어 네이밍 컨벤션과도 맞지 않습니다. TOKEN_NOT_FOUND의 코드를 "AUTH_401_4"로 새로 추가하셨으니, USER_NOT_FOUND 쪽 코드를 "AUTH_404_1" 같은 형태로 변경하는 것을 권장드립니다.

🐛 수정 제안
     TOKEN_NOT_FOUND(HttpStatus.UNAUTHORIZED, "AUTH_401_4", "쿠키에 refreshToken 값이 존재하지 않습니다."),

     // 로그인 실패 (비밀번호 틀림 or 계정 없음)
     LOGIN_FAILED(HttpStatus.UNAUTHORIZED, "AUTH_401_1", "아이디 또는 비밀번호가 일치하지 않습니다."),

-    USER_NOT_FOUND(HttpStatus.NOT_FOUND, "AUTH_401_4", "해당 이메일의 회원이 존재하지 않습니다.")
+    USER_NOT_FOUND(HttpStatus.NOT_FOUND, "AUTH_404_1", "해당 이메일의 회원이 존재하지 않습니다.")
🤖 Fix all issues with AI agents
In
`@src/main/java/com/whereyouad/WhereYouAd/global/exception/GlobalExceptionHandler.java`:
- Around line 88-101: The ExpiredJwtException handler handleExpiredJwtException
is fine but only triggers for the reissue flow because JwtAuthenticationFilter
catches token expiration for header accessTokens (see JwtAuthenticationFilter
and its setErrorResponse usage) while JwtTokenProvider.validateToken re-throws
the exception; update documentation/comments or add a guard to clarify this
behavior: explicitly note in the handleExpiredJwtException comment that this
handler is intended for the /api/auth/reissue path where refreshToken comes from
cookies, and ensure JwtAuthenticationFilter continues to handle header-based
ExpiredJwtException (do not remove its catch), or alternatively delegate to a
shared error response builder if you want identical responses from both
JwtAuthenticationFilter and handleExpiredJwtException (reference
JwtTokenProvider.validateToken, JwtAuthenticationFilter, setErrorResponse, and
handleExpiredJwtException).
🧹 Nitpick comments (2)
src/main/java/com/whereyouad/WhereYouAd/global/exception/GlobalExceptionHandler.java (2)

72-86: MissingRequestCookieException 핸들러가 모든 쿠키 누락에 반응합니다.

현재 이 핸들러는 @CookieValue를 사용하는 모든 엔드포인트에서 쿠키가 누락되면 동일하게 TOKEN_NOT_FOUND ("쿠키에 refreshToken 값이 존재하지 않습니다")를 반환하게 됩니다.

지금은 @CookieValue를 reissue API에서만 사용하고 있다면 문제없지만, 향후 다른 엔드포인트에서도 @CookieValue를 쓰게 되면 엉뚱한 에러 메시지가 나갈 수 있어요.

예를 들어, 쿠키 이름을 체크해서 분기하는 방법이 있습니다:

💡 방어적 개선 제안
 `@ExceptionHandler`(MissingRequestCookieException.class)
 public ResponseEntity<ErrorResponse> handleMissingCookieException(MissingRequestCookieException e, HttpServletRequest request) {
-    log.error("reissue 요청에 쿠키 누락: {}", e.getMessage());
-
-    ErrorResponse errorResponse = ErrorResponse.of(
-            AuthErrorCode.TOKEN_NOT_FOUND,
-            request
-    );
-
-    return ResponseEntity
-            .status(AuthErrorCode.TOKEN_NOT_FOUND.getHttpStatus())
-            .body(errorResponse);
+    log.error("필수 쿠키 누락: {}", e.getCookieName());
+
+    if ("refreshToken".equals(e.getCookieName())) {
+        ErrorResponse errorResponse = ErrorResponse.of(
+                AuthErrorCode.TOKEN_NOT_FOUND,
+                request
+        );
+        return ResponseEntity
+                .status(AuthErrorCode.TOKEN_NOT_FOUND.getHttpStatus())
+                .body(errorResponse);
+    }
+
+    // 다른 쿠키 누락은 일반 Bad Request로 처리
+    ErrorResponse errorResponse = ErrorResponse.of(
+            ErrorCode.INVALID_PARAMETER,
+            request
+    );
+    return ResponseEntity
+            .status(HttpStatus.BAD_REQUEST)
+            .body(errorResponse);
 }

88-101: log.warn에서 JWT 토큰 내용이 로그에 노출될 수 있습니다.

e.getMessage()는 만료된 JWT의 claims 정보(subject, expiration 등)를 포함할 수 있어요. 민감한 사용자 정보가 포함될 수 있으므로, 로그 레벨이나 출력 내용을 한번 확인해보시면 좋겠습니다.

Comment on lines +88 to +101
//reissue API 호출 시 만료된 refreshToken 값으로 접근 시도한 경우 예외 처리
@ExceptionHandler(ExpiredJwtException.class)
public ResponseEntity<ErrorResponse> handleExpiredJwtException(ExpiredJwtException e, HttpServletRequest request) {
log.warn("만료된 JWT refreshToken 입니다: {}", e.getMessage());

ErrorResponse errorResponse = ErrorResponse.of(
AuthErrorCode.TOKEN_EXPIRED, // 만료 전용 에러 코드 사용
request
);

return ResponseEntity
.status(AuthErrorCode.TOKEN_EXPIRED.getHttpStatus())
.body(errorResponse);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for ExpiredJwtException handling and JWT parsing logic

echo "=== Searching for ExpiredJwtException references ==="
rg -n 'ExpiredJwtException' --type=java -B3 -A3

echo ""
echo "=== Searching for JwtTokenProvider or JWT parsing logic ==="
rg -n 'JwtTokenProvider|parseClaimsJws|parseSignedClaims|parserBuilder' --type=java -B2 -A5

echo ""
echo "=== List Java files related to JWT ==="
fd -e java --type f | rg -i jwt

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 18762


🏁 Script executed:

#!/bin/bash
# Search for reissue endpoint and filter routing configuration

echo "=== Searching for reissue endpoint ==="
rg -n -C8 'reissue|refresh' --type=java -i | head -100

echo ""
echo "=== Checking filter configuration and excluded paths ==="
rg -n -C5 'JwtAuthenticationFilter|addFilterBefore|excludePathPatterns' --type=java

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 20746


🏁 Script executed:

#!/bin/bash
# Search for the actual reissue endpoint implementation

echo "=== Searching for reissue/refresh endpoint method ==="
rg -n 'reissue|refresh' --type=java -B5 -A15 | grep -A15 'PostMapping\|GetMapping\|PutMapping\|RequestMapping' | head -80

echo ""
echo "=== Find AuthController or similar ==="
fd -e java -type f | xargs rg -l 'Controller.*auth|auth.*Controller' | head -10

echo ""
echo "=== Check full JwtTokenProvider for exception handling ==="
cat src/main/java/com/whereyouad/WhereYouAd/global/security/jwt/JwtTokenProvider.java | head -120

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 9298


🏁 Script executed:

#!/bin/bash
# Find AuthService.reIssue implementation

echo "=== Searching for reIssue method in AuthService ==="
rg -n -A30 'public.*reIssue' --type=java

echo ""
echo "=== Check if there are other places where validateToken is called ==="
rg -n 'validateToken' --type=java -B2 -A2

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 9273


ExpiredJwtException 핸들러 추가 👍 — 하지만 요청 경로에 따라 다르게 동작합니다.

좋은 소식은 ExpiredJwtException이 재포장(re-wrap)되지 않고 그대로 던져진다는 점입니다. JwtTokenProvider.validateToken()에서 throw e;로 예외를 그대로 전파하고 있으므로 문제없습니다.

다만 실제 흐름을 보면:

  • 일반 요청 (Authorization 헤더의 accessToken): JwtAuthenticationFilter의 catch 블록(line 58-60)에서 먼저 처리되므로, 이 핸들러까지 도달하지 않습니다 (필터가 setErrorResponse()로 직접 응답)
  • reissue 엔드포인트 (/api/auth/reissue): refreshToken이 쿠키에 담겨오기 때문에 필터가 헤더를 검증하지 않아, 서비스 단에서 발생한 ExpiredJwtException이 컨트롤러를 거쳐 이 핸들러에 도달합니다 ✓

따라서 새로운 핸들러는 reissue 엔드포인트에서만 실제로 동작합니다. Spring의 @ExceptionHandler는 가장 구체적인 예외를 우선 매칭하므로 설계가 올바르지만, 일반 accessToken 만료는 여전히 필터에서 처리된다는 점을 인지하고 계신다면 OK입니다 👌

🤖 Prompt for AI Agents
In
`@src/main/java/com/whereyouad/WhereYouAd/global/exception/GlobalExceptionHandler.java`
around lines 88 - 101, The ExpiredJwtException handler handleExpiredJwtException
is fine but only triggers for the reissue flow because JwtAuthenticationFilter
catches token expiration for header accessTokens (see JwtAuthenticationFilter
and its setErrorResponse usage) while JwtTokenProvider.validateToken re-throws
the exception; update documentation/comments or add a guard to clarify this
behavior: explicitly note in the handleExpiredJwtException comment that this
handler is intended for the /api/auth/reissue path where refreshToken comes from
cookies, and ensure JwtAuthenticationFilter continues to handle header-based
ExpiredJwtException (do not remove its catch), or alternatively delegate to a
shared error response builder if you want identical responses from both
JwtAuthenticationFilter and handleExpiredJwtException (reference
JwtTokenProvider.validateToken, JwtAuthenticationFilter, setErrorResponse, and
handleExpiredJwtException).

@ojy0903 ojy0903 changed the title Fix/#34 Fix/#34 reissue API 500 오류 응답 수정 Feb 16, 2026
Copy link
Collaborator

@jinnieusLab jinnieusLab left a comment

Choose a reason for hiding this comment

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

P4: 고생하셨습니다! 토큰 관련 에러를 상세하게 분류해주셔서 좋네요
프론트-백 작업 시 에러 발생하여도 원활하게 소통할 수 있을 듯해 좋은 것 같습니다!

Copy link
Collaborator

@kingmingyu kingmingyu left a comment

Choose a reason for hiding this comment

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

P4: 고생하셨습니다!! 주석 달아주셔서 편하게 볼 수 있었습니다!

@ojy0903 ojy0903 merged commit 66a39f6 into develop Feb 16, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🐛 Bug 버그 수정

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: reissue 오류 - reissue API 에 대한 500 오류 응답

3 participants