乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Spring Security 基础

    • 本章定位
    • Spring Security 是什么
    • DelegatingFilterProxy
    • 安全过滤器链
    • SecurityFilterChain
    • 过滤器执行顺序
  • 第2章 认证

    • 本章定位
    • Authentication
    • 认证流程
    • AuthenticationManager
    • ProviderManager
    • DaoAuthenticationProvider
    • UserDetails
    • UserDetailsService
    • PasswordEncoder
    • BCryptPasswordEncoder
    • DelegatingPasswordEncoder
    • 表单登录
    • SecurityContext
    • SecurityContextHolder
    • UsernamePasswordAuthenticationToken
  • 第3章 授权

    • 本章定位
    • 授权模型
    • GrantedAuthority
    • AccessDecisionManager
    • AccessDecisionVoter
    • URL 级别授权
    • 方法级别安全
    • @PreAuthorize
    • @PostAuthorize
    • @PreFilter
    • @PostFilter
    • @Secured
    • RoleHierarchy
  • 第4章 过滤器链

    • 本章定位
    • FilterChainProxy
    • SecurityContextHolderFilter
    • LogoutFilter
    • BasicAuthenticationFilter
    • CsrfFilter
    • CorsFilter
    • HeaderWriterFilter
    • AnonymousAuthenticationFilter
    • RequestCacheAwareFilter
    • ExceptionTranslationFilter
    • FilterSecurityInterceptor
  • 第5章 会话管理

    • 本章定位
    • 会话管理
    • SessionFixation
    • 会话并发控制
    • SessionCreationPolicy
    • RememberMe
  • 第6章 JWT

    • 本章定位
    • JWT
    • JwtDecoder
    • JWT 认证
    • JwtAuthenticationConverter
  • 第7章 OAuth2

    • 本章定位
    • OAuth2 基础
    • OAuth2 Client
    • OAuth2 Resource Server
    • 第三方登录配置
  • 第8章 攻击防护

    • 本章定位
    • CSRF 跨站请求伪造防护
    • CORS 跨域防护
    • Clickjacking 点击劫持防护
    • 安全响应头
    • Session Fixation 会话固定防护
  • 第9章 测试

    • 本章定位
    • 安全测试
    • @WithMockUser
    • 最佳实践

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 的核心逻辑可简化为以下步骤:

  1. 初始化阶段:在 init() 或首次过滤时,通过 WebApplicationContextUtils.getWebApplicationContext() 获取 Spring 根上下文,然后按 targetBeanName(默认为 springSecurityFilterChain)查找目标 Filter Bean。
  2. 过滤阶段:每次 doFilter() 调用时,将 ServletRequest、ServletResponse 和 FilterChain 原样委托给目标 Bean 的 doFilter()。
  3. 目标 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 的 FilterChainProxy Bean。

示例二: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。
  • 开发者只需编写 SecurityFilterChain Bean 和 UserDetailsService Bean,无需关心 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 上下文中 FilterChainProxy Bean 的名称完全一致。

面试考点:面试官常问 "Servlet 容器中的 Filter 和 Spring 容器中的 Bean 有什么区别?Spring Security 是如何桥接两者的?" 回答要点:

  1. Servlet Filter 由容器实例化,生命周期属于容器;Spring Bean 由 ApplicationContext 管理,可以享受 DI 和 AOP。
  2. DelegatingFilterProxy 是标准的 javax.servlet.Filter,在容器链中占位;初始化时或首次请求时,从 Spring 上下文中按名称查找目标 Bean。
  3. 对于 Spring Security,目标 Bean 是 FilterChainProxy(名称固定为 springSecurityFilterChain)。DelegatingFilterProxy 将 doFilter 调用原样委托给该 Bean,从而把请求引入 Spring Security 的过滤器体系。
  4. 这解决了两个核心问题:一是让 Filter 能注入 Spring Bean;二是让 Spring Security 的核心调度器可以在 Spring 上下文完全启动后再被调用。

版本说明:本章节示例基于 Spring Security 5.x 语法,涵盖传统 web.xml 方式与 Spring Boot 自动配置方式,使用 antMatchers 与 @EnableWebSecurity 等 5.x 典型配置风格。

上一页
Spring Security 是什么
下一页
安全过滤器链