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: 다음 체인 진행
코드 해설
이 단계의 본질은 “Security 로직 자체”가 아니라, Tomcat 필터 체인에서 Spring Security 체인으로 진입하는 관문입니다.
- Tomcat 관점: 표준 Filter 하나 실행
- Spring 관점: 그 Filter가 내부적으로 Security Bean 체인으로 위임
즉 4번은 “보안 정책 수행” 이전의 브리지(Bridge) 단계입니다.
설계 의도
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 제어의 일관성
SecurityFilterAutoConfiguration이 setOrder(...), setDispatcherTypes(...)를 설정하므로, 보안 필터의 위치를 안정적으로 관리할 수 있습니다.
다음 단계 연결
다음 문서 5번에서는 DelegatingFilterProxy가 실제 FilterChainProxy로 위임하고, 여러 Security Filter를 거친 뒤 다시 원래 체인으로 복귀하는 과정을 봅니다.
← 이전: 3. CoyoteAdapter → Pipeline → ApplicationFilterChain | 다음: 5. DelegatingFilterProxy → FilterChainProxy.doFilter()