4. ApplicationFilterChain → DelegatingFilterProxy.doFilter()
Tomcat의 ApplicationFilterChain이 Spring 필터를 직접 알지 않고, 표준 Servlet Filter인 DelegatingFilterProxy를 호출해 Spring Security 진입점을 여는 단계입니다.
코드 기준 결론
- Spring Boot는
SecurityFilterAutoConfiguration.securityFilterChainRegistration(...)에서DelegatingFilterProxyRegistrationBean을 등록한다. - 등록 타깃 이름은
DEFAULT_FILTER_NAME이며 실제 값은springSecurityFilterChain이다. - Tomcat은 일반 필터처럼
DelegatingFilterProxy.doFilter(...)를 호출한다. DelegatingFilterProxy는 Spring 컨테이너에서 타깃 Filter 빈을 조회하고invokeDelegate(...)로 위임한다.- 이 설계 덕분에 컨테이너(Tomcat)와 보안 구현(Spring Security)이 강결합되지 않는다.
오픈소스 호출 흐름
1) Boot가 프록시 필터를 서블릿 체인에 등록
// SecurityFilterAutoConfiguration
private static final String DEFAULT_FILTER_NAME =
AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;
@Bean
@ConditionalOnBean(name = DEFAULT_FILTER_NAME)
DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(...) {
DelegatingFilterProxyRegistrationBean registration =
new DelegatingFilterProxyRegistrationBean(DEFAULT_FILTER_NAME);
registration.setOrder(securityFilterProperties.getOrder());
registration.setDispatcherTypes(getDispatcherTypes(securityFilterProperties));
return registration;
}
핵심은 컨테이너에 직접 FilterChainProxy를 꽂는 게 아니라, DelegatingFilterProxy를 등록한다는 점입니다.
2) 요청 시 Tomcat이 DelegatingFilterProxy 호출
// DelegatingFilterProxy
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
// lazy init
WebApplicationContext wac = findWebApplicationContext();
delegateToUse = initDelegate(wac);
this.delegate = delegateToUse;
}
invokeDelegate(delegateToUse, request, response, filterChain);
}
3) 실제 보안 체인으로 위임
// DelegatingFilterProxy
protected void invokeDelegate(
Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
delegate.doFilter(request, response, filterChain);
}
여기서 delegate가 바로 springSecurityFilterChain 빈이며, 일반적으로 구현체는 FilterChainProxy입니다.
왜 이런 구조인가 (설계 의도)
1. 서블릿 표준 체인과 Spring IoC 체인의 접합부
Tomcat은 Servlet 스펙의 Filter만 이해합니다. Spring Security의 실제 필터 조합은 Spring Bean 생명주기와 의존성 주입이 필요합니다.
DelegatingFilterProxy는 이 둘 사이의 접합 어댑터 역할을 수행합니다.
2. Lazy 초기화로 부팅 순서 문제 완화
DelegatingFilterProxy.doFilter() 내부에서 delegate가 없으면 findWebApplicationContext() 후 initDelegate(...)를 수행합니다.
즉, 필터 인스턴스를 요청 시점에 확보할 수 있어서 컨텍스트 초기화 순서 충돌에 유연합니다.
3. 컨테이너 독립성
보안 로직이 특정 서블릿 컨테이너 구현(Tomcat 내부 API)에 의존하지 않습니다.
delegate.doFilter(...) 계약만 맞추면 Jetty/Undertow에서도 동일하게 동작합니다.
4. 등록 순서/DispatcherType 제어를 Boot가 통합 관리
SecurityFilterAutoConfiguration에서 setOrder(...), setDispatcherTypes(...)를 설정하므로, 보안 필터 위치를 일관되게 유지할 수 있습니다.
시퀀스 다이어그램
(FilterChainProxy) participant Next as 다음 Filter/Servlet AFC->>DFP: doFilter(request, response, chain) alt delegate 미초기화 DFP->>WAC: findWebApplicationContext() DFP->>WAC: initDelegate("springSecurityFilterChain") WAC-->>DFP: FilterChainProxy Bean 반환 else delegate 이미 존재 DFP->>DFP: 캐시된 delegate 사용 end DFP->>SCP: invokeDelegate() -> doFilter(...) SCP-->>DFP: 보안 처리 계속 진행 DFP-->>AFC: 반환 AFC->>Next: 다음 체인 진행
왜 이게 4번의 본질인가
이 단계의 본질은 “Security 로직 자체”가 아니라, Tomcat 필터 체인에서 Spring Security 체인으로 진입하는 관문입니다.
- Tomcat 관점: 표준 Filter 하나 실행
- Spring 관점: 그 Filter가 내부적으로 Security Bean 체인으로 위임
즉 4번은 “보안 정책 수행” 이전의 브리지(Bridge) 단계입니다.