스프링 내부에서 필터가 어떻게 동작하는지 궁금해서 직접 디버깅을 걸고 확인해 보았다.
ApplicationFilterChain 에서 필터들에 대한 흐름제어를 한다.
ApplicationFilterChain 내부에는 Filter 들에대한 배열이 존재한다. 배열을 순차적으로 따라가면서 filter.doFilter 를 호출한다. 그리고, 그 필터의 동작이 끝났으면 ApplicationFilterChain의 doFilter를 호출 한다.
반복문 형태가 아니라 재귀적으로 호출을 이어나가는 형태이다.
화살표로 간단히 흐름을 표시해 보았다.

전체 도식을 그려보았다.

눈으로 직접 확인을 해야 맘이 편하므로 직접 확인해보자.
Spring Boot 3.3.4 기준 Spring Web 에 대한 의존성만을 넣고 디버깅해보았다.

filters[] 배열에 4개의 필터가 들어가 있다.
실제로 서블릿이 호출되기 까지의 과정을 모두 트레이싱 해보면

위에서 본 4개의 필터만이 호출되었음을 알 수 있다.
그런데, 그 외에 OncePerRequestFilter 객체도 계속 보이게 된다.
사실 4개의 필터 모두 OncePerRequestFilter 라는 abstract class 를 상속하는 클래스이다.
스프링에서는 필터가 단 한번 사용되는 것을 보장하기 위해 OncePerRequestFilter 를 상속해서 필터를 사용한다.
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (request instanceof HttpServletRequest httpRequest) {
if (response instanceof HttpServletResponse httpResponse) {
String alreadyFilteredAttributeName = this.getAlreadyFilteredAttributeName();
boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
if (!this.skipDispatch(httpRequest) && !this.shouldNotFilter(httpRequest)) {
if (hasAlreadyFilteredAttribute) {
if (DispatcherType.ERROR.equals(request.getDispatcherType())) {
this.doFilterNestedErrorDispatch(httpRequest, httpResponse, filterChain);
return;
}
filterChain.doFilter(request, response);
} else {
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
// 중요한 부분
this.doFilterInternal(httpRequest, httpResponse, filterChain);
} finally {
request.removeAttribute(alreadyFilteredAttributeName);
}
}
} else {
filterChain.doFilter(request, response);
}
return;
}
}
throw new ServletException("OncePerRequestFilter only supports HTTP requests");
}
protected abstract void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException;
OncePerRequestFilter 의 doFilter 코드는 위와 같다. final 로 선언되어서 자식 클래스에서 상속할 수 없도록 막아두었다. //중요한 부분 이라고 해놓은 부분에서 볼 수 있듯이 자식 필터들에서는 doFilterInternal 을 상속받아서 그곳에서 로직을 작성해주면 된다.
시큐리티 공식 문서에 나오는 도식을 보고 궁금해져서 직접 확인해보았다.


실제로 확인해보니 DelegatingFilterProxyRegistrationBean 이 filter 목록에 추가되었다.