0+ 스프링/0+ 스프링 MVC

[스프링 MVC] 서블릿 필터 예외처리 방법

힘들면힘을내는쿼카 2023. 5. 26. 15:04
728x90
반응형

[스프링 MVC] 서블릿 필터 예외처리 방법

 

 

RestControllerAdvice로 서블릿 필터 예외처리가 안되는 이유 🥲

일반적으로 스프링을 사용하면 ControllerAdviceExceptionHandler를 이용하여 예외처리를 기능을 사용합니다.
(이를 통해 예외가 서블릿으로 전달되지 않고 처리됩니다.^^)

@RestControllerAdvice
public class BasicExceptionHandler {
    @ExceptionHandler(BasicException.class)
    public ResponseEntity<Object> basicExceptionHandle(BasicException e) {
        return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
    }
}

 

그런데…

스프링 시큐리티를 사용하면....
RestControllerAdvice, ExceptionHandler가 동작하지 않는 것을 알 수 있습니다.

 

왜 그럴까요? 😭

 

우리가 주목해야할 점은 스프링 시큐리티는 필터 체인 방식입니다.
그말은 필터를 사용한다는 의미 입니다.

필터...?!

 

요청 흐름

HTTP 요청 -> WAS -> 필터 -> 디스패처 서블릿 -> 컨트롤러

 

여기서 디스패처 전에 필터가 있는것이 보이나요?

저녀석이 바로 서블릿 컨테이너 입니다.!!!!

 

스프링 시큐리티 구조는 필터 체인이다.

 

스프링 시큐리티 공식 문서에서 가져온 그림입니다.
보시는 것처럼 필터를 사용하는 것을 알 수 있습니다.

 

필터는 서블릿 컨테이너의 영역으로
DelegatingProxyFilter의 등장과 스프링 부트에서 내장 톰캣의 사용하여
스프링에서 빈을 등록해서 사용할 수는 있지만, 스프링 컨테이너에서 직접 제어할 수 는 없습니다.

 

따라서 컨트롤러(디스패처 서블릿) 내부에서 발생한 예외는
RestControllerAdvice, ExceptionHandler(인터셉터)를 사용하여 예외 처리를 할 수 있지만,

필터에서 발생한 예외는 필터에서 예외를 처리해야 합니다.^^

 

 

필터 예외 처리

 

테스트를 해봅시다.
필터에서 예외처리를 하기 위해서는 예외가 발생하는 필터의 상위필터에서 예외를 처리해야 합니다.

 

왜 냐구요? 🤔

필터의 흐름을 살펴 보시죠~!

 

필터 흐름

필터1 -> 필터2 -> 필터3
필터1 <- 필터2 <- 필터3

 

위와 같은 순서로 필터가 동작하게 됩니다.

필터3에서 예외가 발생한다면 이보다 상위 필터인 필터1 또는 필터2에서 예외처리를 해야합니다.

그래야 필터2, 필터1에서 예외를 받아서 처리할 수 있겠죠?

필터1 -> 필터2 -> 필터3 (예외 발생!!)
필터1(필터2, 3의 예외 처리 가능) <- 필터2(필터3의 예외 처리 가능) <- 필터3

 

테스트

FirstFilter

@Component
@Order(0) // 필터 호출 순서
public class FirstFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        try {
            System.out.println("FirstFilter 호출");
            chain.doFilter(request, response);
            System.out.println("FirstFilter 종료");
        } catch (RuntimeException e) {
            System.out.println(e.getMessage()+" FirstFilter 가 예외 처리한다.");
            HttpServletResponse servletResponse = (HttpServletResponse) response;
            servletResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            servletResponse.getWriter().println(e.getMessage());
        }
    }
}

 

SecondFilter

@Component
@Order(1) // 필터 호출 순서
public class SecondFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("SecondFilter 호출");
        chain.doFilter(request, response);
        System.out.println("SecondFilter 종료");
    }
}

 

ThirdFilter

@Component
@Order(2) // 필터 호출 순서
public class ThirdFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        throw new RuntimeException("ThirdFilter error!!");
    }
}

 

TestController

@RestController
public class TestController {

    @GetMapping("/")
    public String test() {
        LocalDateTime now = LocalDateTime.now();
        return now.toString();
    }
}

 

결과

 

 

 

 

 

 

 

728x90
반응형