diff --git a/src/main/java/life/mosu/mosuserver/application/auth/provider/JwtTokenProvider.java b/src/main/java/life/mosu/mosuserver/application/auth/provider/JwtTokenProvider.java index acc26266..b51bdb06 100644 --- a/src/main/java/life/mosu/mosuserver/application/auth/provider/JwtTokenProvider.java +++ b/src/main/java/life/mosu/mosuserver/application/auth/provider/JwtTokenProvider.java @@ -106,9 +106,15 @@ protected Claims validateAndParseToken(final String token) { } if (claims.getExpiration().toInstant().isBefore(new Date().toInstant())) { - throw new CustomRuntimeException(ErrorCode.EXPIRED_TOKEN); + throw new ExpiredJwtException( + null, + claims, + "JWT 토큰이 만료되었습니다." + ); } return claims; + } catch (ExpiredJwtException exception) { + throw exception; } catch (JwtException | IllegalArgumentException exception) { throw new CustomRuntimeException(ErrorCode.INVALID_TOKEN_TYPE); } diff --git a/src/main/java/life/mosu/mosuserver/global/filter/TokenExceptionFilter.java b/src/main/java/life/mosu/mosuserver/global/filter/TokenExceptionFilter.java index 8b6911e1..7a3e38ee 100644 --- a/src/main/java/life/mosu/mosuserver/global/filter/TokenExceptionFilter.java +++ b/src/main/java/life/mosu/mosuserver/global/filter/TokenExceptionFilter.java @@ -1,12 +1,17 @@ package life.mosu.mosuserver.global.filter; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.jsonwebtoken.ExpiredJwtException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import life.mosu.mosuserver.global.exception.CustomRuntimeException; +import life.mosu.mosuserver.global.exception.ErrorResponse; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; @@ -14,6 +19,8 @@ @RequiredArgsConstructor public class TokenExceptionFilter extends OncePerRequestFilter { + private final ObjectMapper objectMapper; + @Override protected void doFilterInternal( final HttpServletRequest request, @@ -22,11 +29,43 @@ protected void doFilterInternal( ) throws ServletException, IOException { try { filterChain.doFilter(request, response); - } catch (CustomRuntimeException exception) { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.setContentType("application/json"); + } 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); } } } \ No newline at end of file diff --git a/src/main/java/life/mosu/mosuserver/global/filter/TokenFilter.java b/src/main/java/life/mosu/mosuserver/global/filter/TokenFilter.java index 1ff63eb9..735fa7f8 100644 --- a/src/main/java/life/mosu/mosuserver/global/filter/TokenFilter.java +++ b/src/main/java/life/mosu/mosuserver/global/filter/TokenFilter.java @@ -1,5 +1,6 @@ package life.mosu.mosuserver.global.filter; +import io.jsonwebtoken.ExpiredJwtException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -105,9 +106,12 @@ protected void doFilterInternal( try { setAuthentication(accessToken); + } catch (ExpiredJwtException e) { + log.error("액세스 토큰 만료: {}", e.getMessage()); + throw e; } catch (CustomRuntimeException e) { - log.error("액세스 토큰 인증 실패: {}", e.getMessage()); - throw new CustomRuntimeException(ErrorCode.INVALID_TOKEN); + log.error("유효하지 않은 토큰 인증 실패: {}", e.getMessage()); + throw e; } catch (Exception e) { log.error("액세스 토큰 인증 실패: {}", e.getMessage()); throw new RuntimeException("액세스 토큰 인증 중 예외 발생", e); @@ -120,7 +124,7 @@ private void reissueToken(HttpServletRequest request, HttpServletResponse respon throws IOException { final TokenCookies tokenCookies = tokenResolver.resolveTokens(request); if (!tokenCookies.availableReissue()) { - throw new CustomRuntimeException(ErrorCode.EXPIRED_REFRESH_TOKEN); + throw new CustomRuntimeException(ErrorCode.NOT_FOUND_TOKEN); } Token newToken = authTokenManager.reissueToken(tokenCookies);