Skip to content
过滤器链与执行顺序
概述
Spring Security 将安全处理建模为一组按固定顺序排列的过滤器。过滤器链的顺序直接决定了认证、授权、异常处理等机制的运行结果。实际使用中,大量安全问题并非源于配置项错误,而是过滤器的插入位置不对。
基本概念
Servlet Filter 与 FilterChain
Servlet 规范定义 javax.servlet.Filter 接口,其核心方法为:
java
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)每个过滤器完成自身逻辑后调用 chain.doFilter(),将请求传递给链中的下一个组件。所有过滤器构成一条责任链。
FilterChainProxy 与 SecurityFilterChain
Spring Security 内部使用 FilterChainProxy 作为统一的入口过滤器,其内部委托给一组 SecurityFilterChain。每个 SecurityFilterChain 包含:
- 一个请求匹配器(
RequestMatcher),决定是否处理当前请求; - 一个有序的过滤器列表。
请求到达时,FilterChainProxy 遍历所有 SecurityFilterChain,由第一个成功匹配的链接管后续处理。未被任何链匹配的请求将穿过 Spring Security,交给后续的 Servlet 或静态资源处理。
工作原理
简化的处理路径如下:
HTTP 请求
→ DelegatingFilterProxy
→ FilterChainProxy
→ 遍历 SecurityFilterChain
→ 第一个匹配的链:依次调用其过滤器列表
→ filter1 → filter2 → ... → 控制器每个过滤器在执行完请求阶段的工作后通过 chain.doFilter() 继续向后传递;响应返回时,过滤器可按相反顺序执行 doFilter 之后的代码(通常只添加响应头等操作)。
内置过滤器的默认顺序
Spring Security 在 FilterOrderRegistration 中维护了内置过滤器的顺序表。一条主链路通常包含以下过滤器,按执行顺序排列:
SecurityContextHolderFilter
从SecurityContextRepository中恢复安全上下文,使后续过滤器可以拿到当前身份。HeaderWriterFilter
为响应添加安全头部(如 X-Content-Type-Options、Strict-Transport-Security 等)。CsrfFilter
检查并验证 CSRF 令牌,防范跨站请求伪造攻击。LogoutFilter
拦截注销请求,执行登出逻辑。UsernamePasswordAuthenticationFilter
处理基于表单的登录请求(默认/login),进行用户名/密码认证。BasicAuthenticationFilter
处理 HTTP Basic 认证,提取请求头中的凭证。RequestCacheAwareFilter
恢复被缓存的请求(例如登录前被拦截的页面请求),实现登录后重定向回原页面。SecurityContextHolderAwareRequestFilter
包装HttpServletRequest,提供getUserPrincipal()等 Servlet 安全 API。AnonymousAuthenticationFilter
当SecurityContext中无认证信息时填充一个匿名身份,保证后续组件不会遇到null认证对象。SessionManagementFilter
执行会话固定保护策略,并与SessionAuthenticationStrategy协同。ExceptionTranslationFilter
拦截其后过滤器抛出的AuthenticationException(转换为 401 或登录重定向)和AccessDeniedException(转换为 403),并触发相应的错误响应。AuthorizationFilter
基于AccessDecisionManager进行授权决策,决定是否允许访问。
当启用 OAuth2 登录或资源服务器时,Spring Security 会额外插入过滤器,例如 OAuth2AuthorizationRequestRedirectFilter、OAuth2LoginAuthenticationFilter、BearerTokenAuthenticationFilter 等。这些过滤器的位置通常位于认证区域附近,由框架按预设顺序自动排列。
顺序的设计依据
- 上下文恢复最早:如果
SecurityContextHolderFilter不能最早恢复已认证身份,后续过滤器将始终认为用户未登录。 - 认证先于授权:
AuthorizationFilter依赖SecurityContext中是否存在有效的Authentication对象,因此所有认证过滤器必须在前方执行。 - 异常翻译必须包围授权:
ExceptionTranslationFilter位于AuthorizationFilter之前,才能在授权失败时捕获异常并返回合适的 HTTP 状态码。如果异常翻译放到授权之后,授权异常会穿透并以 500 响应或容器默认错误页呈现。 - 匿名身份晚于真实认证:
AnonymousAuthenticationFilter仅在上下文为空时填充匿名用户。如果它执行在真实认证之前,会将未携带凭证的请求提前固化为匿名主体,导致后续认证过滤器无法再次设置身份。
从执行次序看,请求流可简化为:
请求
→ SecurityContextHolderFilter(恢复身份)
→ CsrfFilter、HeaderWriterFilter 等基础保护
→ 认证过滤器(表单、Basic、JWT 等)
→ AnonymousAuthenticationFilter(若仍无身份则填匿名)
→ ExceptionTranslationFilter(捕获后续异常)
→ AuthorizationFilter(执行授权)
→ 控制器响应沿原路返回,部分过滤器(如 HeaderWriterFilter)在 chain.doFilter() 之后的代码中写入响应头。
基本用法
添加方式
Spring Security 通过 HttpSecurity 提供了三种插入自定义过滤器的方法:
addFilterBefore(Filter, Class<? extends Filter>)
将新过滤器插入到指定过滤器类型之前。addFilterAfter(Filter, Class<? extends Filter>)
插入到指定过滤器类型之后。addFilterAt(Filter, Class<? extends Filter>)
在指定过滤器的位置插入,不删除原有过滤器。若需替换,通常需先移除原过滤器,否则两者都会执行。
常用的参照点
UsernamePasswordAuthenticationFilter— 自定义认证逻辑最常用的参照点。BasicAuthenticationFilter— 若实现自定义 Basic 或 Token 认证,插入此处附近。AuthorizationFilter— 授权区域的边界。ExceptionTranslationFilter— 通常不应把自定义过滤器插在该过滤器之上,以免影响异常处理语义。
插入位置的常见模式
- JWT / API Key 过滤器:通常插入在
UsernamePasswordAuthenticationFilter之前,确保无状态认证优先于表单登录。 - 审计日志过滤器:可放置在认证成功之后、授权之前(例如
BasicAuthenticationFilter之后、AuthorizationFilter之前),便于记录具体操作者。 - 自定义异常包装:不应试图替代
ExceptionTranslationFilter。如需统一包装异常返回格式,可在ExceptionTranslationFilter之前或之后插入包装过滤器,但务必保留原有的异常翻译链路。 - 多安全链:当应用需要针对不同路径实施不同安全策略时,可声明多个
SecurityFilterChainBean。每个链通过@Order或 Bean 定义顺序决定匹配优先级,匹配规则决定哪个链起作用,从而也就决定了该路径下过滤器的组合与顺序。
示例
以下自定义过滤器解析请求头中的 Bearer Token,并设置认证信息。
java
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String authorization = request.getHeader("Authorization");
if (authorization != null && authorization.startsWith("Bearer ")) {
// 实际应用应在此验证签名、过期时间等
Authentication authentication = new UsernamePasswordAuthenticationToken(
"jwtUser", null,
List.of(new SimpleGrantedAuthority("ROLE_USER")));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
}配置类将自定义过滤器插入表单登录过滤器之前,使其优先处理 JWT:
java
@Bean
public SecurityFilterChain apiChain(HttpSecurity http,
JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception {
http
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated());
return http.build();
}这段配置让 JwtAuthenticationFilter 在 UsernamePasswordAuthenticationFilter 之前执行。发送携带 Token 的请求时,JWT 过滤器先解析身份并写入 SecurityContext,后续的认证过滤器检测到上下文已有身份则会跳过,AuthorizationFilter 随后基于该身份进行授权判断。若请求未携带 Token,JWT 过滤器不做处理,请求继续流转至表单认证过滤器(若未匹配登录路径也会跳过),最终由 AnonymousAuthenticationFilter 填充匿名身份,再由 AuthorizationFilter 返回 401 或 403(视配置而定)。
验证过滤链顺序
开启调试日志可查看最终生效的过滤器序列。在 application.yml 中配置:
yaml
logging:
level:
org.springframework.security: DEBUG应用启动后控制台会打印类似信息:
Security filter chain: [
SecurityContextHolderFilter,
HeaderWriterFilter,
CsrfFilter,
LogoutFilter,
JwtAuthenticationFilter,
UsernamePasswordAuthenticationFilter,
BasicAuthenticationFilter,
RequestCacheAwareFilter,
SecurityContextHolderAwareRequestFilter,
AnonymousAuthenticationFilter,
SessionManagementFilter,
ExceptionTranslationFilter,
AuthorizationFilter
]发送一个携带 Token 的请求:
bash
curl -H "Authorization: Bearer eyJ..." http://localhost:8080/api/resource控制台日志可以追踪过滤器执行的先后顺序,确认 JwtAuthenticationFilter 在表单认证之前完成了身份解析。
注意点
- 匹配规则优先:每条
SecurityFilterChain仅处理第一个匹配成功的请求。若多个链都能匹配同一个请求,只有排序最前的链会生效。定义requestMatchers时必须考虑顺序关系。 - 多次
addFilterBefore的顺序:向同一个参照过滤器前连续插入多个自定义过滤器时,后添加的会更靠近参照过滤器,形成“后来居前”的效果。实际顺序需要结合代码书写顺序和调试日志确认。 - 未注册的过滤器不会生效:自定义过滤器必须通过
HttpSecurity的addFilter*方法显式注册才会被纳入过滤链。同样,重复添加同一过滤器实例会导致重复执行。 - 响应阶段的副作用:
HeaderWriterFilter等组件在响应时写入头部。如果误将此类过滤器移除或覆盖,可能导致安全响应头丢失。 - 匿名身份的构建时机:
AnonymousAuthenticationFilter在上下文为空时才填充匿名用户。如果自定义过滤器在匿名过滤器之后执行,却修改了SecurityContext,有可能会造成混乱。自定义认证过滤器通常应置于AnonymousAuthenticationFilter之前。
限制
- 重排内置过滤器的风险:Spring Security 内置过滤器的顺序由框架内部设计决定,手动调整可能导致认证绕过或异常处理失效。除非透彻理解内部依赖关系,否则不应调整内置顺序。
ExceptionTranslationFilter的作用域:该过滤器仅捕获其“下游”过滤器抛出的异常。在它之前插入的过滤器若直接抛出AccessDeniedException,不会被转换,最终会呈现为 500 响应或容器错误页。- 上下文恢复的边界:
SecurityContextHolderFilter负责在请求开始时加载SecurityContext,自定义过滤器不应代替其职责。在异步执行或 Filter 链之外操作SecurityContextHolder时,需注意SecurityContext的线程绑定策略,避免内存泄漏或上下文丢失。 - 响应式栈的差异:采用 WebFlux 的应用使用
WebFilter链替代 Servlet 过滤器链,FilterChainProxy不再参与。以上内容仅适用于基于 Servlet 的 Spring Security 实现。
应用
- 无状态 API 网关:利用自定义 JWT 或 API Key 过滤器,结合
AnonymousAuthenticationFilter,实现完全无 Session 的认证授权。 - 操作审计:在认证与授权之间插入审计过滤器,记录每次访问的用户名称和资源路径。
- 请求预处理:在安全链最前端插入解密、格式转换等过滤器,使后续过滤器能处理标准化后的请求。
- 多租户隔离:为管理后台、开放 API 和内部 RPC 接口创建独立的
SecurityFilterChain,分别配置不同的过滤器和授权策略。
参考链接
- Spring Security 参考文档 — 架构: https://docs.spring.io/spring-security/reference/servlet/architecture.html
- Spring Security 参考文档 — 过滤器排序: https://docs.spring.io/spring-security/reference/servlet/authentication/architecture.html#servlet-authentication-filterordering
- Spring Security 源码(FilterOrderRegistration): https://github.com/spring-projects/spring-security/blob/main/config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterOrderRegistration.java
