제육's 휘발성 코딩
article thumbnail
반응형

Filter 소개 

Spring MVC Flow (출처: https://stackoverflow.com/questions/8000844/spring-handlerinterceptor-vs-servlet-filters)

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

Filter Flow (출처: https://stackoverflow.com/questions/8000844/spring-handlerinterceptor-vs-servlet-filters)

필터는 다음과 같이 여러 개로 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 순서를 지정하지 않았을 때 로그

컴포넌트 스캔을 하면서 컴포넌트 알파벳순으로 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 하위 스캔) 

 

Filter Log (ATestFilter 두 번 호출)

@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 등의 설정을 없앤다.

 

FilterRegistrationBean 필터 로그

지정한 Order 순서대로 필터가 적용되는 것을 확인할 수 있다.

반응형
profile

제육's 휘발성 코딩

@sasca37

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요! 맞구독은 언제나 환영입니다^^