필터란 'HTTP 요청과 응답을 변경할 수 있는 재사용 가능한 클래스'이다. 객체의 형태로 존재하며 클라이언트에서 오는 요청(request)과 최종 자원 사이에 위치하여 클라이언트의 요청 정보를 알맞게 변경할 수 있다. 한 개의 필터만 존재할 수 있는 것은 아니며, 여러 개의 필터가 모여 하나의 필터 체인을 형성할 수 있다.
filter는 스프링에서 관리되지 않고 톰캣과 같은 웹 컨테이너에 의해 관리가 된다. 필터를 추가하기 위해서는 Filter 인터페이스를 구현해주어야 한다.
client에서 요청이 들어왔을 때 가장 먼저 요청을 맞이하는 곳이다. Spring Context 밖에서 정의되므로 Bean을 사용하는 비즈니스 로직과 관련된 작업은 수행할 수 없다. 인코딩, CORS, LOG, 인증 등을 구현하는 목적으로 사용된다.
Spring에서 제공하는 기술로, Dispatcher Servlet에서 controller를 호출하기 전과 후에 요청과 응답을 참조하거나 가공할 수 있는 기능을 제공한다. Spring context 내부에 존재하므로 Spring의 모든 bean 객체에 접근이 가능하다.
1~2번 과정에서 Dispatcher Servlet은 Handler Mapping을 통해 요청에 대해 적절한 Controller를 찾는데 그 결과로 실행 체인(HandlerExecutionChain)을 얻는다. 이 실행 체인은 1개 이상의 Interceptor가 등록되어 있을 경우 순차적으로 Interceptor들을 거쳐 controller가 실행되도록 하고, Interceptor가 없을 경우 바로 Controller를 실행한다.
3번 과정에서 Dispatcher Servlet은 실행할 Controller 정보를 Handler Adapter에게 전달한다. Handler Adapter는 전달받은 Controller를 실행하는데, 실행하기 이전에 Interceptor들을 먼저 실행한다. Handler Adapter가 Controller 메소드 혹은 interceptor를 실행하는 도중 HttpSession을 이용해야 한다면, Servlet Container가 Session Storage를 확인하여 session을 새로 발급하거나 기존의 session을 매핑시켜 준다.
4~5번 과정에서는 controller를 실행하기 전 처리해야 할 전처리 작업이나 요청 정보를 가공하거나 추가하는 작업을 진행한다.
14~15번 과정에서는 controller를 실행한 후 처리해야 할 후처리 작업이 이루어진다.
Interceptor를 추가하기 위해서는 org.springframework.web.servlet의 HandlerInterceptor 인터페이스를 구현해야 한다.
preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) : preHandler 역할을 한다. 3번째 파라미터인 handler는 Handler Mapping이 찾아준 Controller bean에 매핑되는 Handler Method라는 새로운 타입의 객체로, @RequestMapping이 붙은 메소드의 정보를 추상화한 객체이다. return 값이 false일 경우 controller로 흐름이 넘어가지 않고 중단된다.
postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) : postHandler 역할을 한다. 4번째 파라미터인 modelAndView 타입의 정보는 controller로부터 반환된 값이다. 하지만 최근에는 Json 형태로 데이터를 제공하는 RestAPI 기반의 controller를 만들어서 자주 사용되지 않는다.
afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) : 모든 작업이 완료된 후에 실행된다. 요청 처리 중 사용한 리소스를 반환할 때 사용하기에 적합하다.
Filter와 Interceptor는 client로부터 받은 요청을 처리하기 전 전처리, 후처리를 해준다는 면에서는 동일하다. 가장 큰 차이는 언제 처리되는지에 있다.
Filter는 Interceptor보다 앞단에서 동작하며, Spring과 무관한 작업들도 처리가 가능하다. Filter와 Interceptor의 차이와 용도를 잘 정리해둔 표가 있어 포스팅에 참조한다.
Filter는 Spring과 무관한 작업들 처리가 가능하다. 보안 관련 작업이 대표적인 예시인데, 보안검사를 하여 올바른 요청이 아닐 경우 Spring container까지 요청이 전달되지 않게 하여 안정성을 높일 수 있다.
ServletRequest와 ServletResponse 객체를 조작할 수 있다.
Interceptor는 Filter와 달리 ServletRequest와 ServletResponse를 조작할 수는 없다. 대신, 객체가 내부적으로 갖는 값은 조작이 가능하다. 따라서 controller로 넘겨주기 위한 정보를 가공하는 데 사용된다. 예를 들어, JWT 토큰 정보를 파싱해서 controller에게 사용자의 정보를 제공할 수 있도록 가공하는 것이다.
토큰에는 권한 정보만 지니고 있고, 권한으로 무엇을 할 수 있는지에 대한 정보는 DB에 저장되어 있다고 가정한다. 사용자의 권한에 따라 페이지에 대한 접근을 막고자 할 때에는 Filter를 사용해야 하는가? Interceptor를 사용해야 하는가?
답은 Interceptor이다. Filter에서 접근을 막게 되면 DB Connection Pool이 2개가 생겨버려 비효율적인 코드가 된다. Spring에서는 어차피 controller에서 요청을 처리할 때 DB connection이 필요해서 connection pool이 생길 것이다. 그런데 Filter에서 권한에 따른 페이지에 대한 접근을 막기 위해 DB를 조회하게 되어 Connection Pool이 하나 더 생겨버리는 것이다.
IP에 따른 제한을 두려면 Filter를 사용해야 하는가? Interceptor를 사용해야 하는가?
IP 정보는 ServletRequest로부터 제공되므로 IP에 따른 제한을 하는 작업은 Spring 무관하게 처리할 수 있다. 물론, Interceptor로 구현을 해도 된다. 하지만 이 경우 Filter에 걸어 Spring Dispatcher Servlet까지 요청이 도달하는 것을 막는 것이 안정적인 측면에서도, 속도적인 측면에서도 이득이다. 따라서 Filter에 구현해볼 만 한 경우라 할 수 있다.
괜히 Filter와 Interceptor가 각각 존재하는 것이 아니다. 각각의 특징을 잘 알고 어느 때에 Filter를 사용하는지, 어느 때에 Interceptor를 사용해야 하는지, 어떻게 해야 정확하고 효율적인 프로그램이 될 지 항상 고민해야 한다.
[참조] https://mangkyu.tistory.com/173
[참조] https://soon-devblog.tistory.com/4
[참조] https://baek-kim-dev.site/61
[참조] https://velog.io/@ljinsk3/Filter-VS-Interceptor