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

    • 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
    • 最佳实践

安全过滤器链

在 Spring Security 中,"安全过滤器链"(Security Filter Chain)不是指某一个具体的类,而是描述从 Servlet 容器到 Spring Security 核心过滤器之间完整的层级委托关系。理解这条链路的层级结构,是排查"为什么自定义过滤器不生效""为什么 Security 配置没拦截到请求"等问题的关键前提。


定义与作用:没有统一框架时的过滤器困境

在纯 Servlet 时代,开发者通过 web.xml 配置多个 <filter> 和 <filter-mapping> 来组成一条过滤器链。容器按照 filter-mapping 的声明顺序依次调用 doFilter。这种方式存在几个根本性问题:

  • 生命周期割裂:Servlet Filter 由容器实例化,无法注入 Spring 管理的 Bean(如 UserDetailsService)。
  • 顺序难以动态控制:不同模块各自声明过滤器,谁先谁后全凭 web.xml 中的物理顺序,运行时无法根据路径灵活调整。
  • 安全与业务耦合:登录校验、权限判断、日志记录、事务控制全部挤在一条链里,没有清晰的分层。

Spring Security 通过引入两层委托机制,将"Servlet 原生过滤器链"与"Spring Security 安全过滤器链"解耦:前者负责把请求桥接到 Spring 容器,后者负责按路径匹配不同的安全策略。


核心原理:三层过滤器架构

Spring Security 的过滤器链在物理上分为三个层级:

  1. Servlet 容器过滤器链(Container Filter Chain):由 Tomcat/Jetty 等容器维护,执行所有 javax.servlet.Filter。
  2. Spring 过滤器代理层(DelegatingFilterProxy → FilterChainProxy):DelegatingFilterProxy 是容器链中的一环,它将请求委托给 Spring 容器中的 FilterChainProxy Bean。
  3. Security 过滤器链(SecurityFilterChain):FilterChainProxy 内部维护多个 SecurityFilterChain 实例,每个实例包含一组有序的安全过滤器(如 UsernamePasswordAuthenticationFilter、AuthorizationFilter 等),并可根据请求路径独立匹配。

关键结论:

  • DelegatingFilterProxy 只出现一次,固定在容器过滤器链中。
  • FilterChainProxy 只出现一次,作为 Spring Bean 存在,但它内部管理着多条 SecurityFilterChain。
  • 请求到达时,FilterChainProxy 从上到下遍历所有 SecurityFilterChain,第一个匹配当前请求路径的链被激活,执行其内部过滤器序列。
  • 未匹配任何 SecurityFilterChain 的请求,直接放行到后续 Servlet 过滤器或目标资源。

示例一:配置两条独立的安全过滤器链

场景说明

某应用同时提供 REST API(/api/**)和 Web 管理后台(/admin/**)。API 采用 HTTP Basic 认证且无 Session,后台采用表单登录且有 Session。两者需要完全不同的安全过滤器组合。

操作前配置:单链混用方案(混乱且不可维护)

如果所有请求共用一条 SecurityFilterChain,开发者不得不在同一条链里同时启用表单登录和 Basic 认证,导致未认证请求的行为不可预测:

@Bean
public SecurityFilterChain mixedChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth
            .antMatchers("/api/**").authenticated()
            .antMatchers("/admin/**").hasRole("ADMIN")
            .anyRequest().permitAll()
        )
        .formLogin(Customizer.withDefaults())      // 对 API 也生效,不合适
        .httpBasic(Customizer.withDefaults());     // 对后台也生效,冗余
    return http.build();
}

问题分析:API 请求未携带认证信息时,UsernamePasswordAuthenticationFilter 会尝试将其重定向到 /login 页面,而不是返回 401,这不符合 REST API 的语义。

操作后配置:多链分离方案

@Configuration
@EnableWebSecurity
public class MultiChainSecurityConfig {

    @Bean
    @Order(1) // 数字越小优先级越高
    public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exception {
        http
            .securityMatcher("/api/**") // 5.4+ 支持,5.x 也可用 requestMatcher
            .authorizeHttpRequests(auth -> auth
                .anyRequest().authenticated()
            )
            .httpBasic(Customizer.withDefaults())
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            );
        return http.build();
    }

    @Bean
    @Order(2)
    public SecurityFilterChain adminSecurityFilterChain(HttpSecurity http) throws Exception {
        http
            .securityMatcher("/admin/**")
            .authorizeHttpRequests(auth -> auth
                .anyRequest().hasRole("ADMIN")
            )
            .formLogin(Customizer.withDefaults())
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
            );
        return http.build();
    }

    @Bean
    @Order(3)
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .antMatchers("/", "/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(Customizer.withDefaults());
        return http.build();
    }
}

结果分析

请求路径匹配的链生效的认证方式Session 策略
/api/usersapiSecurityFilterChainHTTP Basic无状态
/admin/dashboardadminSecurityFilterChain表单登录按需创建
/homedefaultSecurityFilterChain表单登录按需创建
  • 通过 @Order 控制链的匹配优先级,避免宽泛的 /** 被提前匹配而覆盖后续规则。
  • securityMatcher 让每条链只关注自己的路径范围,过滤器组合互不干扰。

示例二:自定义过滤器插入安全过滤器链

场景说明

需要在所有请求进入认证和授权逻辑之前,先记录一条安全审计日志(如记录请求来源 IP 和访问目标)。该日志组件需要注入 Spring 的 HttpServletRequest 工具类,因此不能注册为原生 Servlet Filter(否则生命周期在容器层,无法享受 Spring DI)。

操作前配置:错误地注册为 Servlet Filter

@Component
@WebFilter(urlPatterns = "/*") // 错误!这会注册到 Servlet 容器,而非 Security 链
public class AuditLogFilter implements Filter {
    @Autowired
    private AuditLogService auditLogService; // 可能注入失败或延迟

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        auditLogService.log(((HttpServletRequest) req).getRequestURI());
        chain.doFilter(req, res);
    }
}

问题分析:@WebFilter 让 Servlet 容器在 Spring 启动完成前就实例化过滤器,此时 AuditLogService 可能尚未就绪;且该过滤器独立于 Spring Security 链,无法精确控制它在 Security 上下文加载之前还是之后执行。

操作后配置:将自定义过滤器加入 SecurityFilterChain

public class AuditLogFilter extends OncePerRequestFilter {
    @Autowired
    private AuditLogService auditLogService;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                      HttpServletResponse response,
                                      FilterChain filterChain) throws ServletException, IOException {
        auditLogService.log(request.getRemoteAddr(), request.getRequestURI());
        filterChain.doFilter(request, response);
    }
}

在配置类中将其插入 SecurityFilterChain:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    private AuditLogFilter auditLogFilter;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .addFilterBefore(auditLogFilter, UsernamePasswordAuthenticationFilter.class)
            .authorizeHttpRequests(auth -> auth
                .antMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(Customizer.withDefaults());
        return http.build();
    }
}

结果分析

  • 使用 addFilterBefore 将 AuditLogFilter 插入到 UsernamePasswordAuthenticationFilter 之前,保证审计日志在认证逻辑之前记录。
  • 由于 AuditLogFilter 是 Spring Bean,可以正常注入 AuditLogService 和其他依赖。
  • 该过滤器只在 SecurityFilterChain 生效的请求中执行,不会干扰静态资源或未被 Security 拦截的路径。

易错场景与面试考点

易错场景:自定义过滤器被容器和 Spring Security 各执行一次

很多开发者在自定义过滤器上同时加了 @Component(或 @WebFilter)和 addFilterBefore,导致过滤器在容器过滤器链和 Security 过滤器链中各出现一次,请求被处理两次。

错误代码:

@Component // 被 Spring Boot 自动扫描并注册到 Servlet 容器
public class MyFilter extends OncePerRequestFilter { ... }

// 配置类中又手动加入 Security 链
http.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter.class);

正确做法:

// 1. 如果走 Security 链,则不要加 @WebFilter,也不要让 Spring Boot 自动注册为 Servlet Filter
// 2. 仅通过配置类加入 SecurityFilterChain
public class MyFilter extends OncePerRequestFilter { ... }

// 配置类
http.addFilterBefore(new MyFilter(), UsernamePasswordAuthenticationFilter.class);
// 或者如果 MyFilter 需要注入依赖,可以声明为 @Component,但务必确认没有 @WebFilter

面试考点:面试官常问 "Spring Security 的过滤器链与 Servlet 过滤器链有什么区别?" 回答要点:

  1. Servlet 过滤器链由容器管理,按 web.xml 或 @WebFilter 顺序执行;Spring Security 的过滤器链由 FilterChainProxy 管理,按 SecurityFilterChain 内部顺序执行。
  2. DelegatingFilterProxy 是两者的唯一交汇点:它是 Servlet 链中的一环,负责将请求委托给 Spring 容器中的 FilterChainProxy。
  3. 多个 SecurityFilterChain 可以共存,按 @Order 和 securityMatcher 匹配;而 Servlet 过滤器链没有这种按路径动态分支的机制。

版本说明:本章节示例基于 Spring Security 5.x 语法,使用 securityMatcher、antMatchers 以及 addFilterBefore 等 5.x 典型配置风格。

上一页
DelegatingFilterProxy
下一页
SecurityFilterChain