JWT Refresh Token에 대해 공부를 하던 도중, 어느 부분에서 doFilter를 호출하는 것이 아닌, return을 호출하게 됐다.
필터는 무조건 FilterChain#doFilter()를 호출하라고 배웠던 것 같은데, return을 호출하면 어떻게 되는지 감이 잡히질 않았다.
Filter에 대해서 잠깐 알아보고, return과 doFilter()의 차이점에 대해 알아보겠다.
DispatcherServlet이라고 볼 수 있다.public interface Filter {
default void init(FilterConfig filterConfig) throws ServletException {
}
void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
default void destroy() {
}
}
GenericFilterBean은 스프링의 설정 정보를 포함하고 있는 필터이다.
doFilter 메소드를 오버라이드해야된다.
public class TestFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {}
}
OncePerRequestFilter는 GenericFilterBean를 상속받은 클래스이며, 요청당 한 번만 실행되는 특성을 가지고 있다.한 번의 요청에 의도치않게 필터가 여러 번 실행되는 것을 방지해준다.
doFilterInternal 메소드를 오버라이드해야된다.
public class TestFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {}
}

/* 이다.
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<Filter> logFilter() {
FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
bean.setFilter(new LogFilter());
bean.setOrder(1);
bean.addUrlPatterns("/*");
return bean;
}
@Bean
public FilterRegistrationBean<Filter> loginCheckFilter() {
FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
bean.setFilter(new LoginCheckFilter());
bean.setOrder(2);
bean.setUrlPatterns(List.of("/1", "/2"));
return bean;
}
}
@Order로 순서를 지정해줄 수 있다.@Slf4j
@Component
@Order(1)
public class LogFilter implements Filter {
@Override
public void init(
FilterConfig filterConfig
) throws ServletException {
log.info("LogFilter init()");
}
@Override
public void destroy() {
log.info("LogFilter destroy()");
}
@Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain
) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
String requestURI = req.getRequestURI();
log.info("[{}] LogFilter doFilter Start", requestURI);
try {
chain.doFilter(request, response);
} finally {
log.info("[{}] LogFilter doFilter End", requestURI);
}
}
}
@WebFilter를 통해 적용될 URL을 설정해줄 수 있다.@WebFilter + @ServletComponentScan 방식으로도 사용할 수 있지만, 이는 필터간에 순서를 보장하지 않는다.@Slf4j
@WebFilter({"/login", "/items"})
@Component
@Order(1)
public class LogFilter implements Filter {
@Override
public void init(
FilterConfig filterConfig
) throws ServletException {
log.info("LogFilter init()");
}
@Override
public void destroy() {
log.info("LogFilter destroy()");
}
@Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain
) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
String requestURI = req.getRequestURI();
log.info("[{}] LogFilter doFilter Start", requestURI);
try {
chain.doFilter(request, response);
} finally {
log.info("[{}] LogFilter doFilter End", requestURI);
}
}
}
doFilter()의 호출 횟수가 FilterChain에 등록된 Filter의 개수와 같아지는 시점에 서블릿을 호출하게 된다.
doFilter()의 종료 시점에 서블릿을 호출하는 것이 아니다.doFilter를 호출한 뒤, return을 호출하면 서블릿을 호출하지 못하게 되며 그 상태로 필터들이 하나씩 return되기 시작하면서 default status code인 200과 텅 빈 body가 response로 client에게 내려간다.스프링과 스프링 시큐리티에서의 FilterChain 구현체인 VirtualFilterChain을 보면, 다음과 같이 currentPosition과 size(등록된 필터의 개수)가 같을 때 originalChain의 doFilter()를 호출한다.
originalChain은 개발자가 등록한 필터가 들어있지 않은, 기본적으로 등록된 필터 체인을 의미하는 것 같다.

다음 필터가 존재하지 않을 시, PassThroughFilterChain에서 서블릿을 호출하는 모습을 볼 수 있다.

@Component // default order = 2147483647 // 가장 뒤에 위치한 필터
@Slf4j
public class TestFilter1 implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
log.info("TestFilter1 호출됨");
//로직이 끝났으니 return 되어 TestFilter1을 호출한 TestFilter2의 chain.doFilter 가 종료된다.
}
}
@Component
@Slf4j
@Order(value = 2147483646)
public class TestFilter2 implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
log.info("TestFilter2 호출됨");
chain.doFilter(request, response); // 실행순서 2번
log.info("TestFilter2 호출됨(after DoFilter[1])"); // API 호출 전 실행
chain.doFilter(request, response); // 실행순서 3번 -> API 호출
// 등록한 필터가 3개이므로, 3번의 doFilter가 호출되면 서블릿을 호출하게 된다.
// 만약, doFilter를 여기서 한 번 더 호출하게 된다면, 서블릿을 다시 재호출하게 된다.
log.info("TestFilter2 호출됨(after DoFilter[2])"); // API 호출 후 실행
//모든 로직 실행 후 return -> TestFilter3의 chain.doFilter()로 진행
}
}
@Component
@Slf4j
@Order(value = 2147483645)
public class TestFilter3 implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
log.info("TestFilter3 호출됨");
chain.doFilter(request, response); // 실행순서 1번
log.info("TestFilter3 호출됨(after DoFilter)"); // API 호출 후 실행
}
}

https://www.inflearn.com/chats/786858/filter%EB%A5%BC-%EB%93%B1%EB%A1%9D%ED%95%98%EB%8A%94-4%EA%B0%80%EC%A7%80-%EB%B0%A9%EB%B2%95
https://developer-ping9.tistory.com/237
https://velog.io/@jmjmjmz732002/Springboot-OncePerRequestFilter-vs-GenericFilterBean