Spring/문제 해결 (Troubleshooting)

@ControllerAdvice가 Filter 예외를 잡지 못하는 문제 해결

가지코딩 2025. 6. 4. 11:29

문제 상황

  • JWT 인증을 위한 커스텀 필터(JwtFilter)에서 토큰 유효성을 검사할 때, 아래와 같이 JwtValidationException이라는 커스텀 예외를 던지도록 구현하였다
 catch (SecurityException | MalformedJwtException e) {
    throw new JwtValidationException("유효하지 않는 JWT 서명입니다.", HttpServletResponse.SC_UNAUTHORIZED);
} catch (ExpiredJwtException e) {
    throw new JwtValidationException("만료된 JWT 토큰입니다.", HttpServletResponse.SC_UNAUTHORIZED);
} catch (UnsupportedJwtException e) {
    throw new JwtValidationException("지원되지 않는 JWT 토큰입니다.", HttpServletResponse.SC_BAD_REQUEST);
} catch (Exception e) {
    throw new JwtValidationException("내부 서버 오류", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}

 

그리고 @ControllerAdvice를 통해 예외를 전역 처리하려 했다:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(JwtValidationException.class)
    public ResponseEntity<ErrorResponse> handleJwtException(JwtValidationException e) {
        return ResponseEntity
                .status(e.getHttpStatus())
                .body(new ErrorResponse(e.getMessage()));
    }

    // ... 기타 핸들러
}

 

하지만 Filter에서 발생한 예외는 위 핸들러로 처리되지 않고, Spring Security 기본 에러 페이지 또는 빈 응답으로 리턴되었다.


2. 원인 분석

  • Spring에서 @ControllerAdvice는 DispatcherServlet 이후의 흐름에서 발생한 예외만을 처리한다.
  • 반면, Filter는 DispatcherServlet 앞단에 위치하기 때문에, 여기서 발생한 예외는 @ControllerAdvice까지 도달하지 않는다.
[Filter] → DispatcherServlet → Controller → @ControllerAdvice
   ↑
   예외는 여기서 끝남

3. 해결 방법

  • Filter에서 발생한 예외는 직접 HttpServletResponse에 에러 응답을 작성하는 방식으로 처리하였다.
  • 추가로, 예외 메시지와 HTTP 상태코드를 일관된 방식으로 관리할 수 있도록 enum 을 추가했다.
catch (SecurityException | MalformedJwtException | TokenNotFoundException e) {
    response.sendError(ServletResponseError.INVALID_JWT_SIGNATURE.getHttpStatus(), ServletResponseError.INVALID_JWT_SIGNATURE.getMessage());
} catch (ExpiredJwtException e) {
    response.sendError(ServletResponseError.EXPIRED_JWT_TOKEN.getHttpStatus(), ServletResponseError.EXPIRED_JWT_TOKEN.getMessage());
} catch (UnsupportedJwtException e) {
    response.sendError(ServletResponseError.UNSUPPORTED_JWT.getHttpStatus(), ServletResponseError.UNSUPPORTED_JWT.getMessage());
} catch (Exception e) {
    response.sendError(ServletResponseError.INTERNAL_SERVER_ERROR.getHttpStatus(), ServletResponseError.INTERNAL_SERVER_ERROR.getMessage());
}