DelegatingFilterProxy
DelegatingFilterProxy(委托过滤器代理)是 Spring Security 与 Servlet 容器之间的第一道桥梁。它本身是一个标准的 javax.servlet.Filter,在容器过滤器链中占据一个位置,但它并不真正执行安全逻辑,而是将所有的过滤工作委托给 Spring 应用上下文中的某个 Bean。
定义与作用:为什么需要委托代理
在没有 Spring Security 的纯 Spring Web 项目中,如果开发者想要编写一个既参与过滤器链又能享受 Spring 依赖注入的过滤器,会遇到一个棘手的问题:Servlet 容器实例化 Filter 的时机早于 Spring 容器启动完成。这意味着:
- 手写
Filter在init()中无法通过ApplicationContext.getBean()稳定获取依赖。 - 即使通过
DelegatingFilterProxy的前身思路(手动写ContextLoaderListener集成),代码也极其冗长。 - 每个需要 Spring DI 的过滤器都要重复编写同样的桥接代码。
Spring 的解决方案是提供一个通用的委托代理:在 Servlet 容器层面只注册一个 DelegatingFilterProxy,代理在初始化时从 Spring 容器中按名称查找真正的目标 Filter Bean(对于 Spring Security 而言,该 Bean 就是 FilterChainProxy,名称固定为 springSecurityFilterChain)。此后所有请求都由代理转发给 Spring 管理的 Bean,彻底解决了生命周期和依赖注入的冲突。
核心结论:DelegatingFilterProxy 的设计本质是生命周期桥接器——它让 Servlet 容器中的 Filter 插槽能够调用 Spring 容器中的 Bean,从而统一享受 Spring 的依赖注入、AOP 和事务管理。
核心原理
DelegatingFilterProxy 的核心逻辑可简化为以下步骤:
- 初始化阶段:在
init()或首次过滤时,通过WebApplicationContextUtils.getWebApplicationContext()获取 Spring 根上下文,然后按targetBeanName(默认为springSecurityFilterChain)查找目标 Filter Bean。 - 过滤阶段:每次
doFilter()调用时,将ServletRequest、ServletResponse和FilterChain原样委托给目标 Bean 的doFilter()。 - 目标 Bean 执行:对于 Spring Security,目标 Bean 是
FilterChainProxy,它会进一步调度匹配的SecurityFilterChain。
// DelegatingFilterProxy 核心逻辑(简化示意)
public class DelegatingFilterProxy extends GenericFilterBean {
private volatile Filter delegate; // 目标 Filter Bean 的缓存
private String targetBeanName = "springSecurityFilterChain";
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// 延迟初始化或直接使用已缓存的 delegate
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
WebApplicationContext wac = findWebApplicationContext();
delegateToUse = wac.getBean(targetBeanName, Filter.class);
this.delegate = delegateToUse;
}
// 委托给 Spring 容器中的 Bean
delegateToUse.doFilter(request, response, filterChain);
}
}
示例一:传统 web.xml 配置方式
场景说明
在 Spring Security 5.x 之前的传统 Spring MVC(SSM)项目中,没有 Spring Boot 的自动配置,开发者需要手动在 web.xml 中注册 DelegatingFilterProxy,以将 Spring Security 接入 Servlet 容器。
操作前配置:缺少代理,安全 Bean 无法生效
如果直接注册 FilterChainProxy(这是 Spring Security 的核心调度器):
<!-- 错误示例:不要直接注册 FilterChainProxy -->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.security.web.FilterChainProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
问题分析:FilterChainProxy 是 Spring Bean,它的依赖(如 SecurityFilterChain 列表、AuthenticationManager 等)需要在 Spring 容器启动后才能注入。但 Servlet 容器在启动时就会实例化 FilterChainProxy,此时 Spring 可能尚未完成刷新,导致 NullPointerException 或配置不完整。
操作后配置:使用 DelegatingFilterProxy 桥接
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!-- Spring 根上下文配置 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/application-context.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Spring Security 入口:DelegatingFilterProxy -->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
对应的 Spring XML 配置(application-context.xml):
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<security:http>
<security:intercept-url pattern="/admin/**" access="hasRole('ADMIN')"/>
<security:form-login/>
</security:http>
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:user name="admin" password="{noop}admin123" authorities="ROLE_ADMIN"/>
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
</beans>
结果分析
web.xml中的<filter-name>必须是springSecurityFilterChain,因为DelegatingFilterProxy默认按此名称去 Spring 上下文中查找 Bean。DelegatingFilterProxy由容器实例化,但它只在doFilter时懒加载目标 Bean,此时 Spring 上下文已就绪,因此FilterChainProxy及其所有依赖都能完整注入。- 当 Spring XML 中定义
<security:http>时,Spring Security 命名空间处理器会自动创建名为springSecurityFilterChain的FilterChainProxyBean。
示例二:Spring Boot 自动配置下的隐藏细节
场景说明
在使用 Spring Boot 的现代化项目中,开发者通常看不到 web.xml,也无需手动声明 DelegatingFilterProxy。但这并不意味着它不存在,而是被 Spring Boot 的自动配置隐藏了。理解这个隐藏过程,对排查"自定义 Filter 为什么顺序不对""自动配置为什么没有生效"等问题至关重要。
操作前配置:手动重复注册(导致代理冲突)
某些开发者从传统项目迁移到 Spring Boot 时,习惯性地在配置类中手动注册一个 DelegatingFilterProxy:
@Configuration
public class LegacyFilterConfig {
@Bean
public FilterRegistrationBean<DelegatingFilterProxy> securityFilter() {
FilterRegistrationBean<DelegatingFilterProxy> registration = new FilterRegistrationBean<>();
registration.setFilter(new DelegatingFilterProxy("springSecurityFilterChain"));
registration.addUrlPatterns("/*");
registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registration;
}
}
问题分析:Spring Boot 的 SecurityFilterAutoConfiguration 已经自动注册了一个 DelegatingFilterProxy。如果配置类中再手动注册一个,容器中会出现两个 DelegatingFilterProxy 实例,导致 Spring Security 的安全过滤器被重复执行两次,所有请求都会经历两轮认证和授权检查,性能受损且行为异常。
操作后配置:信任自动配置,通过 SecurityFilterChain 定制
在 Spring Boot + Spring Security 5.x 项目中,正确的做法是完全不碰 DelegatingFilterProxy,只关注配置 SecurityFilterChain:
@Configuration
@EnableWebSecurity
public class BootSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.antMatchers("/public/**").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.permitAll()
)
.logout(Customizer.withDefaults());
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("admin")
.password("{noop}admin123")
.roles("ADMIN")
.build());
manager.createUser(User.withUsername("user")
.password("{noop}user123")
.roles("USER")
.build());
return manager;
}
}
启动日志中可以看到自动配置的成果(Spring Boot 2.x / Spring Security 5.x 风格):
o.s.s.web.DefaultSecurityFilterChain : Will secure any request with
[org.springframework.security.web.session.DisableEncodeUrlFilter@...,
org.springframework.security.web.context.SecurityContextHolderFilter@...,
org.springframework.security.web.header.HeaderWriterFilter@...,
org.springframework.security.web.csrf.CsrfFilter@...,
org.springframework.security.web.authentication.logout.LogoutFilter@...,
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@...,
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@...,
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@...,
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@...,
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@...,
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@...,
org.springframework.security.web.access.ExceptionTranslationFilter@...,
org.springframework.security.web.access.intercept.AuthorizationFilter@...]
结果分析
- Spring Boot 的
SecurityFilterAutoConfiguration在启动时会自动创建一个FilterRegistrationBean,将DelegatingFilterProxy注册到 Servlet 容器,名称为springSecurityFilterChain。 - 开发者只需编写
SecurityFilterChainBean 和UserDetailsServiceBean,无需关心DelegatingFilterProxy和FilterChainProxy的装配过程。 - 如果确实需要调整
DelegatingFilterProxy的Order(如让它排在某些第三方过滤器之后),应修改spring.security.filter.order属性,而不是手动再注册一个FilterRegistrationBean。
易错场景与面试考点
易错场景:<filter-name> 或 targetBeanName 拼写错误
无论是传统 web.xml 还是手动注册 FilterRegistrationBean,DelegatingFilterProxy 查找 Spring Bean 的依据是名称匹配。如果名称拼写错误,Spring Security 将完全失效,且不会在启动期抛出明显异常(它只会在第一个请求到来时才懒加载失败)。
错误写法:
<!-- web.xml 中拼写错误 -->
<filter>
<filter-name>springSecurityFilterChain</filter-name> <!-- 正确 -->
</filter>
// 手动注册时使用了错误的 targetBeanName
new DelegatingFilterProxy("securityFilterChain"); // 错误!应该是 springSecurityFilterChain
正确写法:
web.xml中的<filter-name>必须是springSecurityFilterChain(Spring Security 默认 Bean 名称)。- 如果通过
new DelegatingFilterProxy()的带参构造传入名称,必须与 Spring 上下文中FilterChainProxyBean 的名称完全一致。
面试考点:面试官常问 "Servlet 容器中的 Filter 和 Spring 容器中的 Bean 有什么区别?Spring Security 是如何桥接两者的?" 回答要点:
- Servlet Filter 由容器实例化,生命周期属于容器;Spring Bean 由
ApplicationContext管理,可以享受 DI 和 AOP。DelegatingFilterProxy是标准的javax.servlet.Filter,在容器链中占位;初始化时或首次请求时,从 Spring 上下文中按名称查找目标 Bean。- 对于 Spring Security,目标 Bean 是
FilterChainProxy(名称固定为springSecurityFilterChain)。DelegatingFilterProxy将doFilter调用原样委托给该 Bean,从而把请求引入 Spring Security 的过滤器体系。- 这解决了两个核心问题:一是让 Filter 能注入 Spring Bean;二是让 Spring Security 的核心调度器可以在 Spring 上下文完全启动后再被调用。
版本说明:本章节示例基于 Spring Security 5.x 语法,涵盖传统
web.xml方式与 Spring Boot 自动配置方式,使用antMatchers与@EnableWebSecurity等 5.x 典型配置风格。