Filter 소개
Filter는 애플리케이션 레벨에서 로그인, 시큐리티, Jwt 등 구현을 하다 보면 Dispatcher Servlet에 도달하기 전 요청에서 토큰 검사 등 유효성 검사를 처리하기 위해 사용되며 Chaining 방식으로 여러 개의 필터를 설정할 수 있습니다.
필터는 Spring Context 내부에 도달하기 전인 WAS 인입 시점에 로직을 처리해 줍니다.
Filter 인터페이스를 살펴보면 doFilter 메서드가 보이는데, 이 메서드가 다음에 체이닝 할 필터가 있으면 다음 필터로 넘겨주고, 없으면 서블릿을 호출해 줍니다.
public interface Filter {
public default void init(FilterConfig filterConfig) throws ServletException{}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException;
public default void destroy() {}
}
- init() : 필터 초기화 메서드로 서블릿 컨테이너가 생성될 때 호출
- doFilter() : 요청이 올 때마다 해당 메서드를 호출하며 필터 로직을 구현하는 부분
- destroy() : 필터 종료 메서드로 서블릿 컨테이너가 종료될 때 호출
Filter Chain
필터는 다음과 같이 여러 개로 Chaining 설정할 수 있어서 클라이언트의 요청마다 URL 패턴 등을 지정해서 필터링할 수 있다.
OncePerRequestFilter는 Filter Chain이 여러 번 실행되더라도 요청 당 한 번만 필터를 수행하도록 처리해 주는 역할을 합니다.
Filter Order
필터의 순서는 Ordered 인터페이스를 구현한 @Order 어노테이션 또는 커스텀 구현 클래스를 사용합니다.
public interface Ordered {
/**
* Useful constant for the highest precedence value.
* @see java.lang.Integer#MIN_VALUE
*/
int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
/**
* Useful constant for the lowest precedence value.
* @see java.lang.Integer#MAX_VALUE
*/
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
int getOrder();
}
- 값이 작을수록 우선순위가 높게 설정되어 있으며, 값의 범위는 int 값으로 지정되어 있네요. (디폴트는 MAX_VALUE)
Filter 적용 방식
Filter 적용 방식에는 @WebServletComponentScan, @Component, FilterRegistrationBean 등록 방식이 있다.
먼저 @WebServletComponentScan, @Component을 살펴보자.
@WebServletComponentScan, @Component 방식
@Component
@Order(Integer.MIN_VALUE)
@Slf4j
public class ATestFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
log.info("ATEST FILTER START");
chain.doFilter(request, response);
log.info("ATEST FILTER END");
}
}
- Filter의 우선순위를 1순위인 Inter.MIN_VALUE로 지정
Chaining 방식으로 여러 개를 설정한 뒤 Order 어노테이션을 지정하지 않으면 Filter의 순서는 어떻게 실행될까?
컴포넌트 스캔을 하면서 컴포넌트 알파벳순으로 Filter Chaining이 지정된다. 만약 web.xml에서 filter-mapping을 지정하였다면 지정한 순서대로 적용될 것이다.
@Component
@Order(Integer.MIN_VALUE)
@WebFilter(urlPatterns = "/api") // 적용 안됨
@Slf4j
public class ATestFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
log.info("ATEST FILTER START");
chain.doFilter(request, response);
log.info("ATEST FILTER END");
}
}
- @WebFilter를 사용하면 URL 패턴을 지정할 수 있는데, 실행해 보면 URL 패턴이 적용되지 않고 모든 URL에 적용이 돼버린다.
@SpringBootApplication
@ServletComponentScan // 임베디드 톰캣에서 필터, 서블릿, 리스너를 빈으로 등록
public class MessageBotApplication {
public static void main(String[] args) {
SpringApplication.run(MessageBotApplication.class, args);
}
}
- Application에 @ServletComponentScan을 지정하면 @WebFilter에 명시한 URL 패턴이 적용된다. (임베디드 톰캣 설정)
- @ServletComponentScan은 필터에서 @WebFilter, 서블릿에서 @WebServlet, 리스너에서 @WebListener 등의 어노테이션을 사용할 수 있도록 처리해 준다. (default는 basePackages 하위 스캔)
@ServletComponentScan을 설정하고 실행을 해보니 @WebFilter를 사용한 ATestFilter가 두 번 호출되었다.
그 이유는 @ServletComponentScan과 @Component 어노테이션 두 곳에서 컴포넌트 스캔 과정에서 필터로 등록해 두었기 때문이다. 따라서 URL 패턴 적용과 한 번의 필터 호출을 위해 @Component를 제거해 줘야 한 번만 호출되도록 설정할 수 있다.
FilterRegistrationBean 방식
public abstract class RegistrationBean implements ServletContextInitializer, Ordered {
private static final Log logger = LogFactory.getLog(RegistrationBean.class);
private int order = Ordered.LOWEST_PRECEDENCE; // Integer.MAX_VALUE
// ...
}
- FilterRegistrationBean 방식은 직접 필터를 빈으로 등록하는 방식이다. RegistrationBean 추상 클래스를 상속받아 사용한다.
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean AFilter() {
FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
filterFilterRegistrationBean.setFilter(new ATestFilter());
filterFilterRegistrationBean.setOrder(Integer.MIN_VALUE);
filterFilterRegistrationBean.addUrlPatterns("/*");
return filterFilterRegistrationBean;
}
@Bean
public FilterRegistrationBean BFilter() {
FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
filterFilterRegistrationBean.setFilter(new BTestFilter());
filterFilterRegistrationBean.setOrder(5000);
filterFilterRegistrationBean.addUrlPatterns("/*");
return filterFilterRegistrationBean;
}
@Bean
public FilterRegistrationBean CFilter() {
FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
filterFilterRegistrationBean.setFilter(new CTestFilter());
filterFilterRegistrationBean.setOrder(-100);
filterFilterRegistrationBean.addUrlPatterns("/*");
return filterFilterRegistrationBean;
}
}
- FilterRegistrationBean 객체를 생성해서 Filter 정보를 만들어서 넣어주면 된다.
- 이때 주의해야 할 점은 Filter는 반드시 구현되어 있어야 한다. (Order와 UrlPatterns은 생략 가능), @Component, @WebServlet 등의 설정을 없앤다.
지정한 Order 순서대로 필터가 적용되는 것을 확인할 수 있다.