Skip to content

MOSU-344 refactor: 토큰 만료 핸들링#345

Merged
wlgns12370 merged 3 commits intodevelopfrom
refactor/mosu-344
Aug 12, 2025
Merged

MOSU-344 refactor: 토큰 만료 핸들링#345
wlgns12370 merged 3 commits intodevelopfrom
refactor/mosu-344

Conversation

@wlgns12370
Copy link
Contributor

@wlgns12370 wlgns12370 commented Aug 12, 2025

✨ 구현한 기능

📢 논의하고 싶은 내용

  • x

🎸 기타

  • x

Summary by CodeRabbit

  • Bug Fixes

    • Expired tokens now return a dedicated error and HTTP status (406) instead of a generic invalid token, improving clarity for clients.
    • More accurate propagation of token errors prevents misclassification during authentication.
  • Refactor

    • Standardized all authentication error responses to JSON with consistent HTTP statuses and UTF-8 encoding.
    • Unified error formatting enables easier client parsing and handling.
    • Improved logging and handling paths for token validation and reissue scenarios.

@wlgns12370 wlgns12370 self-assigned this Aug 12, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 12, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Refactors token expiration handling across JWT provider and filters: JwtTokenProvider now throws ExpiredJwtException on expiration; TokenFilter propagates ExpiredJwtException and adjusts other exception rethrows; TokenExceptionFilter serializes JSON error responses for CustomRuntimeException, ExpiredJwtException, and generic exceptions.

Changes

Cohort / File(s) Summary
JWT provider: expiration exception behavior
src/main/java/life/mosu/mosuserver/application/auth/provider/JwtTokenProvider.java
On expired tokens, throw ExpiredJwtException with claims/message; explicitly catch and rethrow ExpiredJwtException; other JWT/IllegalArgumentException map to CustomRuntimeException(INVALID_TOKEN_TYPE).
Filters: propagation and JSON error responses
src/main/java/life/mosu/mosuserver/global/filter/TokenFilter.java, src/main/java/life/mosu/mosuserver/global/filter/TokenExceptionFilter.java
TokenFilter: adds catch/rethrow for ExpiredJwtException; rethrows CustomRuntimeException instead of wrapping; changes reissueToken error from EXPIRED_REFRESH_TOKEN to NOT_FOUND_TOKEN when unavailable. TokenExceptionFilter: injects ObjectMapper; returns JSON error responses for CustomRuntimeException (401), ExpiredJwtException (406), and others (500).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant TF as TokenFilter
  participant JP as JwtTokenProvider
  participant TEF as TokenExceptionFilter
  participant RS as Response

  Client->>TF: HTTP Request with Access Token
  TF->>JP: setAuthentication(token)
  JP-->>TF: ExpiredJwtException
  TF-->>TEF: propagate ExpiredJwtException
  TEF->>RS: 406 JSON { code: TOKEN_EXPIRED, message }
  RS-->>Client: Response
Loading
sequenceDiagram
  autonumber
  actor Client
  participant TF as TokenFilter
  participant JP as JwtTokenProvider
  participant TEF as TokenExceptionFilter
  participant RS as Response

  Client->>TF: HTTP Request with invalid token
  TF->>JP: setAuthentication(token)
  JP-->>TF: JwtException / IllegalArgumentException
  TF-->>TEF: propagate CustomRuntimeException(INVALID_TOKEN_TYPE)
  TEF->>RS: 401 JSON { code, message }
  RS-->>Client: Response
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Assessment against linked issues

Objective Addressed Explanation
토큰 만료 핸들링 (#344)

Possibly related PRs

Suggested labels

우선순위 중

Poem

A whisk of code, a hop through time,
My token’s clock now chimes, “expire!”
Filters squeak the JSON rhyme,
406 or 401—wire by wire.
I twitch my ears, exceptions tamed,
And bound along—refactors named. 🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f888537 and 323484e.

📒 Files selected for processing (3)
  • src/main/java/life/mosu/mosuserver/application/auth/provider/JwtTokenProvider.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/global/filter/TokenExceptionFilter.java (2 hunks)
  • src/main/java/life/mosu/mosuserver/global/filter/TokenFilter.java (3 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/mosu-344

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Summary of Changes

Hello @wlgns12370, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 PR은 인증 필터 내에서 토큰 만료 및 일반적인 에러 핸들링 로직을 리팩토링합니다. ExpiredJwtException에 대한 특정 처리를 도입하여 만료된 토큰에 대해 더 세분화된 에러 응답을 제공하고, TokenExceptionFilter에서 ObjectMapper를 사용하여 에러 응답을 표준화된 JSON 형식으로 반환합니다. 또한, TokenFilter의 예외 흐름을 조정하여 ExpiredJwtException을 전파하고, 리프레시 토큰 재발급 불가 시 사용되는 에러 코드를 개선합니다.

Highlights

  • JWT 토큰 만료 예외 처리 개선: JwtTokenProvider에서 토큰 만료 시 CustomRuntimeException 대신 ExpiredJwtException을 발생시키도록 변경하여, 토큰 만료에 대한 보다 구체적인 예외 처리가 가능해졌습니다.
  • 필터 내 에러 응답 표준화: TokenExceptionFilter에서 CustomRuntimeException, ExpiredJwtException, 그리고 일반 Exception에 대해 각각 다른 HTTP 상태 코드와 함께 표준화된 JSON 형식의 에러 응답을 제공하도록 개선되었습니다. ObjectMapper를 사용하여 응답 본문을 작성합니다.
  • 토큰 필터 예외 전파 로직 수정: TokenFilter에서 ExpiredJwtException을 명시적으로 캐치하여 로그를 남기고 재전파함으로써, TokenExceptionFilter에서 해당 예외를 정확히 처리할 수 있도록 변경되었습니다. 또한, 리프레시 토큰 재발급 실패 시 NOT_FOUND_TOKEN 에러 코드를 사용하도록 수정되었습니다.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@wlgns12370 wlgns12370 merged commit d9928bf into develop Aug 12, 2025
2 of 3 checks passed
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

안녕하세요. 토큰 만료 및 필터 예외 처리 리팩토링 PR 리뷰입니다.
전반적으로 ExpiredJwtException을 도입하여 만료된 토큰 예외를 명시적으로 처리하고, TokenExceptionFilter를 통해 필터단에서 발생하는 예외를 일관되게 처리하도록 개선한 점이 좋습니다.
다만, TokenExceptionFilter에서 일부 코드 중복과 로깅 누락, HTTP 상태 코드 사용에 대한 개선점이 보입니다. 또한 JwtTokenProvider의 불필요한 catch 블록을 정리하면 코드가 더 깔끔해질 것 같습니다.
자세한 내용은 각 파일의 코드 리뷰 댓글을 참고해주세요.

.code(ex.getCode())
.build();

response.setStatus(HttpStatus.UNAUTHORIZED.value());

Choose a reason for hiding this comment

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

high

CustomRuntimeException은 자체적으로 HttpStatus를 가지고 있습니다. 하지만 여기서는 HttpStatus.UNAUTHORIZED로 상태 코드를 고정하고 있어, 예외에 담긴 정확한 상태 코드가 무시됩니다. ex.getStatus().value()를 사용하여 동적으로 상태 코드를 설정해야 올바른 오류 응답을 클라이언트에 전달할 수 있습니다.

Suggested change
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setStatus(ex.getStatus().value());

Comment on lines +116 to 118
} catch (ExpiredJwtException exception) {
throw exception;
} catch (JwtException | IllegalArgumentException exception) {

Choose a reason for hiding this comment

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

medium

catch (ExpiredJwtException exception) 블록은 예외를 잡아서 아무런 추가 작업 없이 다시 던지고 있습니다. 이는 불필요하며 코드를 복잡하게 만듭니다. 이 catch 블록을 제거해도 동작은 동일하게 유지되면서 코드가 더 간결해집니다.

Comment on lines +32 to 69
} catch (CustomRuntimeException ex) {

ErrorResponse errorResponse = ErrorResponse.builder()
.status(ex.getStatus().value())
.message(ex.getMessage())
.code(ex.getCode())
.build();

response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");

objectMapper.writeValue(response.getWriter(), errorResponse);
} catch (ExpiredJwtException ex) {
ErrorResponse errorResponse = ErrorResponse.builder()
.status(HttpStatus.NOT_ACCEPTABLE.value())
.message("토큰이 만료되었습니다.")
.code("TOKEN_EXPIRED")
.build();

response.setStatus(HttpStatus.NOT_ACCEPTABLE.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
response.sendError(exception.getStatus().value(), exception.getMessage());

objectMapper.writeValue(response.getWriter(), errorResponse);
} catch (Exception ex) {
ErrorResponse errorResponse = ErrorResponse.builder()
.status(HttpStatus.INTERNAL_SERVER_ERROR.value())
.message("서버 오류가 발생했습니다.")
.code("INTERNAL_SERVER_ERROR")
.build();

response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");

objectMapper.writeValue(response.getWriter(), errorResponse);
}

Choose a reason for hiding this comment

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

medium

현재 catch 블록들에는 코드 중복과 로깅 누락이라는 두 가지 개선점이 있습니다.

  1. 코드 중복: 세 개의 catch 블록에서 ErrorResponse를 생성하고 응답을 설정하는 코드가 거의 동일합니다. 이 로직을 별도의 private 메서드로 추출하면 중복을 줄이고 코드 가독성과 유지보수성을 높일 수 있습니다.
  2. 로깅 누락: catch (Exception ex) 블록에서 예외를 로깅하지 않고 있습니다. 예상치 못한 오류를 추적하고 디버깅하기 어렵게 만들 수 있으므로, 클래스에 @Slf4j를 추가하고 log.error("...")를 사용하여 예외를 기록하는 것이 중요합니다.

아래는 리팩토링 예시입니다.

// private helper method
private void writeErrorResponse(HttpServletResponse res, HttpStatus status, String msg, String code) throws IOException {
    res.setStatus(status.value());
    res.setContentType(MediaType.APPLICATION_JSON_VALUE);
    res.setCharacterEncoding("UTF-8");
    ErrorResponse errRes = ErrorResponse.builder()
            .status(status.value()).message(msg).code(code).build();
    objectMapper.writeValue(res.getWriter(), errRes);
}

// catch (Exception ex) block
// log.error("Unhandled exception in filter chain", ex);
// writeErrorResponse(response, HttpStatus.INTERNAL_SERVER_ERROR, "서버 오류가 발생했습니다.", "INTERNAL_SERVER_ERROR");

Comment on lines +46 to +56
ErrorResponse errorResponse = ErrorResponse.builder()
.status(HttpStatus.NOT_ACCEPTABLE.value())
.message("토큰이 만료되었습니다.")
.code("TOKEN_EXPIRED")
.build();

response.setStatus(HttpStatus.NOT_ACCEPTABLE.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
response.sendError(exception.getStatus().value(), exception.getMessage());

objectMapper.writeValue(response.getWriter(), errorResponse);

Choose a reason for hiding this comment

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

medium

만료된 토큰에 대해 HttpStatus.NOT_ACCEPTABLE (406)을 반환하고 있습니다. 이는 일반적으로 클라이언트의 Accept 헤더와 서버가 생성할 수 있는 컨텐츠 타입이 맞지 않을 때 사용됩니다. 토큰이 만료된 경우에는 인증 실패를 의미하는 HttpStatus.UNAUTHORIZED (401)를 사용하는 것이 더 일반적이고 표준에 가깝습니다. 이전 구현에서도 UNAUTHORIZED를 사용했던 만큼, 일관성을 위해 변경을 고려해보시는 것이 좋겠습니다.

Suggested change
ErrorResponse errorResponse = ErrorResponse.builder()
.status(HttpStatus.NOT_ACCEPTABLE.value())
.message("토큰이 만료되었습니다.")
.code("TOKEN_EXPIRED")
.build();
response.setStatus(HttpStatus.NOT_ACCEPTABLE.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
response.sendError(exception.getStatus().value(), exception.getMessage());
objectMapper.writeValue(response.getWriter(), errorResponse);
ErrorResponse errorResponse = ErrorResponse.builder()
.status(HttpStatus.UNAUTHORIZED.value())
.message("토큰이 만료되었습니다.")
.code("TOKEN_EXPIRED")
.build();
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
objectMapper.writeValue(response.getWriter(), errorResponse);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[🛠 리팩토링] 토큰 만료 핸들링

1 participant

Comments