Filter 단계에서 예외 발생 시 오류 응답 누락 트러블슈팅 - KimGyuBek/Threadly GitHub Wiki

문제 상황

Authentication filter에서 예외가 터지면 CustomAuthenticationEntryPoint에서 감지되지 않는 문제 발생


원인 분석

필터 등록 순서 문제

//SecurityConfig.java
/*VerificationFilter*/
    http.addFilterBefore(verificationFilter, UsernamePasswordAuthenticationFilter .class);

/*UserStatusTypeValidationFilter*/
    http.

addFilterBefore(userStatusTypeValidationFilter, VerificationFilter .class);

/*jwt authentication filter*/
    http.

addFilterBefore(jwtAuthenticationFilter, UserStatusTypeValidationFilter .class);

필터 실행 순서:

JwtAuthenticationFilter -> UserStatusTypeValidationFilter -> VerificationFilter -> UsernamePasswordAuthenticationFilter -> ... -> ExceptionTranslationFilter

  • 커스텀 필터가 ExceptionTranslationFilter보다 앞에 위치해있다.
  • 결과적으로 ExceptionTranslationFilter가 호출 되지 않아서 예외 처리가 되지 않고 있다.

ExceptionTranslationFilter가 예외를 받아서 AuthenticationEntrypoint로 넘겨야 하는데, 필터 순서가 앞이라 그 구간까지 못 간다. 따라서 예외 핸들링이 되지 않고 있음.


해결 과정

1. 필터 내부에서 AuthenticationEntryPoint.commence() 직접 호출 후 필터 종료한다.

//JwtAuthenticationFilter.java
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
    FilterChain filterChain) throws ServletException, IOException {
  try {
//    ...

    /*사용자 관련 오류 */
  } catch (UserException e) {
    UserAuthenticationException exception = new UserAuthenticationException(e.getErrorCode());
    authenticationEntryPoint.commence(request, response, exception);
    return;
//              ...
  }
}

응답은 오지만 filter에서 발생한 예외는 공통 응답 포맷이 적용되지 않는 문제 발생

원인 분석

//ResponseWrapperAdvice.java
@RestControllerAdvice()
public class ResponseWrapperAdvice implements ResponseBodyAdvice<Object> {

  //...
  @Override
  public Object beforeBodyWrite(Object body, MethodParameter returnType,
      MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType,
      ServerHttpRequest request, ServerHttpResponse response) {
//    ...
    if (body instanceof ErrorResponse) { //GlobalExceptionHandler에서 예외 감지 후 ErrorResponse로 변환 후 넘어오면 공통 응답 바디로 감싼다.
      return ApiResponse.fail(((ErrorResponse) body).getErrorCode());
    }

    return ApiResponse.success(body);
  }
}

filter에서 예외가 터지면 요청이 DispatcherServlet까지 못 간다. 그래서 ResponseBodyAdvice가 동작하지 않고 공통 응답 바디( ApiResponse.fail())로 감싸지 못 해 응답 형태가 달라진다.


2. AuthenticationEntryPoint에서 직접 공통 응답 바디(ApiResponse.fail())을 조립한다.

//CustomAuthenticationEntryPoint.java
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

  //  ...
  @Override
  public void commence(HttpServletRequest request, HttpServletResponse response,
      AuthenticationException authException) throws IOException, ServletException {
//    ...
    /*TokenAuthenticationException일 경우*/
    if (authException instanceof TokenAuthenticationException) {
      ErrorCode errorCode = ((TokenAuthenticationException) authException).getErrorCode();

      response.setStatus(errorCode.getHttpStatus().value());
      objectMapper.writeValue(response.getWriter(),
          ApiResponse.fail(errorCode));

      return;
    }
  }
}

CustomAuthenticationEntrypoint에서 응답 바디를 직접 만들어서 리턴


결론

1. ExceptionTranslationFilter 이후에 등록된 필터에서 AuthenticationException 발생 시

exception_flow_01

2. ExceptionTranslationFilter 이전에 등록된 필터에서 AuthenticationException 발생 시

exception_flow_02

ExceptionTranslatorFilter 이전에 등록된 Filter에서 발생한 예외는 단순히 throw만 해서는 ExceptionTranslationFilter까지 도달하지 못한다.
그 결과 AuthenticationEntryPoint가 실행되지 않아 실패 응답이 내려가지 않고, @ResponseBodyAdvice 역시 적용되지 않는다.

따라서 해결책은 두 가지다:

  • 필터 내부에서 직접 AuthenticationEntryPoint.commence()를 호출하고 체인을 종료한다.
  • 또는 가능하다면 커스텀 필터를 ExceptionTranslationFilter 뒤에 등록해 예외를 위임한다.

따라서 일반 예외와 Filter 예외 모두 ApiResponse.fail()을 통해 동일한 응답 포맷을 보장할 수 있게 되었다.

⚠️ **GitHub.com Fallback** ⚠️