Filter에서는 왜 GlobalExceptionHandler가 동작하지 않을까?
Spring Boot로 로그인 기능을 구현하면서 예상치 못한 문제를 만났다.
로그인을 하지 않은 사용자가 접근하면, 필터에서 예외를 던져 GlobalExceptionHandler에서 처리되길 기대했는데... 실제로는 그게 작동하지 않는것이다ㅠㅠㅠ
그래서 이 글에서는 다음과 같은 흐름으로 문제를 정리해보려 합니다:
GlobalExceptionHandler란?
Filter에서 예외를 던졌는데 왜 잡히지 않을까?
예외를 잡기 위한 대안 방법
Spring에서는 @ControllerAdvice와 @ExceptionHandler를 이용해 전역 예외 처리를 할 수 있습니다.
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(CustomUnauthorizedException.class)
public ResponseEntity<String> handleUnauthorized(CustomUnauthorizedException ex) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(ex.getMessage());
}
}
컨트롤러나 서비스에서 예외가 발생하면 이 핸들러가 동작해 클린한 에러 응답을 보낼 수 있죠.
하지만 중요한 것은,,
이 방식은 DispatcherServlet 이후의 흐름에서만 동작한다는 것!
Filter는 Spring MVC의 DispatcherServlet보다 앞단에서 동작합니다.
즉, 컨트롤러 로직에 도달하기도 전에 실행되는 레벨!
Client → Filter → DispatcherServlet → Controller → Service
@ControllerAdvice가 동작하는 영역은 DispatcherServlet 이후!
➡️ Filter에서 발생한 예외는 GlobalExceptionHandler까지 도달하지 않음...
즉, 예외는 던졌지만, 그것을 잡을 애가 없었던 것!!!!
Filter에서 response에 직접 에러 응답을 작성하는 방법이 있다
public class AuthFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
HttpSession session = req.getSession(false);
if (session == null || session.getAttribute("user") == null) {
res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
res.setContentType("application/json;charset=UTF-8");
res.getWriter().write("{\"message\": \"로그인이 필요합니다.\"}");
return;
}
chain.doFilter(request, response);
}
}
예외를 던지는 게 아니라, 직접 클라이언트에게 메시지를 작성하는 방식
만약 @ControllerAdvice와 함께 예외 처리를 하고 싶다면,
Filter 대신 HandlerInterceptor 를 사용하는 것도 좋은 대안입니다.
Interceptor는 DispatcherServlet 이후에 동작하기 때문에 GlobalExceptionHandler에서 예외를 잡을 수 있다!~
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute("user") == null) {
throw new CustomUnauthorizedException("로그인이 필요합니다.");
}
return true;
}
}
그리고 이렇게 던진 예외는 GlobalExceptionHandler에서 정상적으로 처리됩니다.
Filter는 DispatcherServlet보다 앞단이라 GlobalExceptionHandler가 동작하지 않는다.
Filter에서는 예외 대신 response를 직접 작성해야 한다.
GlobalExceptionHandler를 활용하고 싶다면 HandlerInterceptor를 고려하자.