Conversation
…RefreshToken 값이 만료된 경우 예외 처리 추가
Walkthroughreissue API에서 쿠키 누락 시 발생하는 500 에러를 해결하기 위해 새로운 인증 오류 코드와 전역 예외 핸들러를 추가했습니다. MissingRequestCookieException과 ExpiredJwtException을 처리하여 400대 오류로 응답하도록 개선했습니다. Changes
Estimated code review effort🎯 1 (Trivial) | ⏱️ ~3 minutes 리뷰 피드백✅ 잘 짠 부분:
🚥 Pre-merge checks | ✅ 5 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts (beta)
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 |
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/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_FOUND는HttpStatus.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 등)를 포함할 수 있어요. 민감한 사용자 정보가 포함될 수 있으므로, 로그 레벨이나 출력 내용을 한번 확인해보시면 좋겠습니다.
| //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); | ||
| } |
There was a problem hiding this comment.
🧩 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 jwtRepository: 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=javaRepository: 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 -120Repository: 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 -A2Repository: 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).
jinnieusLab
left a comment
There was a problem hiding this comment.
P4: 고생하셨습니다! 토큰 관련 에러를 상세하게 분류해주셔서 좋네요
프론트-백 작업 시 에러 발생하여도 원활하게 소통할 수 있을 듯해 좋은 것 같습니다!
kingmingyu
left a comment
There was a problem hiding this comment.
P4: 고생하셨습니다!! 주석 달아주셔서 편하게 볼 수 있었습니다!
📌 관련 이슈
🚀 개요
reissue API 관련 오류 수정
📄 작업 내용
📸 스크린샷 / 테스트 결과 (선택)
===기존 오류 응답===
===수정 이후 응답===
✅ 체크리스트
🔍 리뷰 포인트 (Review Points)
Summary by CodeRabbit
릴리스 노트