在 Spring Security 的架构中,
FilterChainProxy是整个安全过滤器链的"总指挥"和"总入口"。它本身并不直接处理具体的认证或授权逻辑,而是负责请求的防火墙校验、智能路由分发、生命周期管理以及异常兜底 。本文将结合 Spring Security 6.5.x 正式源码,深度拆解FilterChainProxy及其构建者WebSecurity的核心运行机制。
前言
在前两篇文章中,我们理清了过滤器的执行顺序(FilterOrderRegistration)和构建过程(HttpSecurity → DefaultSecurityFilterChain)。但一个关键问题还没回答:
构建好的过滤器链,是如何被"激活"的?每个 HTTP 请求进来后,到底是谁在调度这些过滤器? 答案就是 FilterChainProxy------Spring Security 的"总代理"。
一、FilterChainProxy:Spring Security 的总入口
在标准的 Spring Boot 应用中,Servlet 容器(如 Tomcat)并不直接感知 Spring Security 内部庞大的过滤器链。它们之间通过 DelegatingFilterProxy 进行桥接:
- Servlet 容器 只知道一个名为
springSecurityFilterChain的标准 Servlet Filter。 DelegatingFilterProxy拦截请求,并将其委托给 Spring IoC 容器中真正的 Bean ------FilterChainProxy。FilterChainProxy内部维护了一个List<SecurityFilterChain>,根据请求的 URL 短路匹配(short-circuit matching) 智能路由到具体的过滤器链执行:遍历所有 SecurityFilterChain,第一个匹配的生效,后续的忽略。
说明 :
SecurityFilterChain接口只有两个方法:matches(HttpServletRequest)和getFilters()。matches()用于判断当前请求是否属于该链的管辖范围,getFilters()返回该链所包含的 Spring Security 过滤器列表。由于采用短路匹配机制,必须将最具体的匹配规则放在最前面 (例如/admin/**应排在/**之前)。
二、WebSecurity:FilterChainProxy 的构建者
FilterChainProxy 并不是凭空产生的,它是由 WebSecurity 在 Spring 容器初始化时构建出来的。WebSecurity 继承了 AbstractConfiguredSecurityBuilder<Filter, WebSecurity> 并实现了 SecurityBuilder<Filter> 接口,其核心构建逻辑在 performBuild() 方法中。
2.1 核心职责
- 收集并构建所有的
SecurityFilterChain(由HttpSecurity构建出来的每条安全过滤器链)。 - 反向提取 所有链中的授权规则,构建全局的授权评估器(
WebInvocationPrivilegeEvaluator)。 - 组装
FilterChainProxy并注入防火墙(HttpFirewall)和拒绝处理器(RequestRejectedHandler)。 - 自动感知 Spring 容器中的扩展组件(
ObservationRegistry、HttpFirewallBean、FilterChainDecorator后处理器等)。
2.2 容器初始化阶段的自动装配 (WebSecurity.setApplicationContext)
在 performBuild() 真正执行之前,WebSecurity 通过 setApplicationContext() 从 Spring IoC 容器中自动感知 以下可选组件(源码位于 WebSecurity.java 第 240-265 行附近):
| 自动装配项 | 说明 |
|---|---|
HttpFirewall Bean |
若容器中存在用户自定义的 HttpFirewall Bean,则注入;否则为空(留待 FilterChainProxy 使用自身默认的 StrictHttpFirewall) |
RequestRejectedHandler Bean |
若容器中存在用户自定义的拒绝处理器,则注入 |
ObservationRegistry Bean |
若引入 Micrometer,则自动获取观察注册表;否则使用 ObservationRegistry.NOOP |
ObjectPostProcessor<FilterChainDecorator> |
用于后处理 VirtualFilterChainDecorator,Observability 模块正是通过此机制注入 ObservationFilterChainDecorator |
HttpServletRequestTransformer Bean |
用于在 PrivilegeEvaluator 评估前对请求进行转换(如包装请求) |
2.3 实际构建阶段 (WebSecurity.performBuild)
当 Spring 容器初始化 springSecurityFilterChain Bean 时,会触发 webSecurity.build(),最终调用 WebSecurity.performBuild()。
java
@Override
protected Filter performBuild() {
Assert.state(!this.securityFilterChainBuilders.isEmpty(),
() -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. "
+ "Typically this is done by exposing a SecurityFilterChain bean. "
+ "More advanced users can invoke " + WebSecurity.class.getSimpleName()
+ ".addSecurityFilterChainBuilder directly");
int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();
List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);
// 构建授权管理器 Builder,用于后续反向提取所有链的授权规则
RequestMatcherDelegatingAuthorizationManager.Builder builder =
RequestMatcherDelegatingAuthorizationManager.builder();
boolean mappings = false;
// ① 处理 ignored requests(官方强烈不推荐)
for (RequestMatcher ignoredRequest : this.ignoredRequests) {
this.logger.warn("You are asking Spring Security to ignore " + ignoredRequest
+ ". This is not recommended -- please use permitAll via HttpSecurity#authorizeHttpRequests instead.");
// 创建"空过滤器链":仅含 RequestMatcher,filters 列表为空
SecurityFilterChain securityFilterChain = new DefaultSecurityFilterChain(ignoredRequest);
securityFilterChains.add(securityFilterChain);
// 将 ignored 规则同步加入授权管理器(标记为 permitAll)
builder.add(ignoredRequest, SingleResultAuthorizationManager.permitAll());
mappings = true;
}
// ② 构建所有用户定义的 SecurityFilterChain
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) {
SecurityFilterChain securityFilterChain = securityFilterChainBuilder.build();
securityFilterChains.add(securityFilterChain);
// 提取链中的授权规则,同步加入全局授权管理器
// 兼容两种授权过滤器:AuthorizationFilter(6.x 推荐)和 FilterSecurityInterceptor(5.x 遗留)
mappings = addAuthorizationManager(securityFilterChain, builder) || mappings;
}
// ③ 构建 WebInvocationPrivilegeEvaluator(专门用于 JSP <sec:authorize> 等视图层标签)
if (this.privilegeEvaluator == null) {
AuthorizationManager<HttpServletRequest> authorizationManager = mappings ? builder.build()
: SingleResultAuthorizationManager.permitAll();
AuthorizationManagerWebInvocationPrivilegeEvaluator privilegeEvaluator =
new AuthorizationManagerWebInvocationPrivilegeEvaluator(authorizationManager);
privilegeEvaluator.setServletContext(this.servletContext);
if (this.privilegeEvaluatorRequestTransformer != null) {
privilegeEvaluator.setRequestTransformer(this.privilegeEvaluatorRequestTransformer);
}
this.privilegeEvaluator = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
List.of(new RequestMatcherEntry<>(AnyRequestMatcher.INSTANCE, List.of(privilegeEvaluator))));
}
// ④ 创建 FilterChainProxy 并注入所有链
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
// ⑤ 配置防火墙
// 注意:如果用户在容器中定义了 HttpFirewall Bean,则优先使用;
// 否则 FilterChainProxy 内部默认使用 StrictHttpFirewall(通过成员变量初始化)
if (this.httpFirewall != null) {
filterChainProxy.setFirewall(this.httpFirewall);
}
// ⑥ 配置请求拒绝处理器(支持 Micrometer 可观测性)
if (this.requestRejectedHandler != null) {
filterChainProxy.setRequestRejectedHandler(this.requestRejectedHandler);
}
else if (!this.observationRegistry.isNoop()) {
// 如果引入了 Micrometer,自动包装观测处理 + HTTP 400 响应的组合处理器
CompositeRequestRejectedHandler requestRejectedHandler = new CompositeRequestRejectedHandler(
new ObservationMarkingRequestRejectedHandler(this.observationRegistry),
new HttpStatusRequestRejectedHandler());
filterChainProxy.setRequestRejectedHandler(requestRejectedHandler);
}
// 如果以上条件都不满足,FilterChainProxy 使用自身默认的 HttpStatusRequestRejectedHandler
// ⑦ 验证、装饰与初始化
filterChainProxy.setFilterChainValidator(new WebSecurityFilterChainValidator());
filterChainProxy.setFilterChainDecorator(getFilterChainDecorator());
filterChainProxy.afterPropertiesSet();
// ⑧ 如果开启 debug,包装 DebugFilter 并打印安全警告
Filter result = filterChainProxy;
if (this.debugEnabled) {
this.logger.warn("\n\n" + "********************************************************************\n"
+ "********** Security debugging is enabled. *************\n"
+ "********** This may include sensitive information. *************\n"
+ "********** Do not use in a production system! *************\n"
+ "********************************************************************\n\n");
result = new DebugFilter(filterChainProxy);
}
this.postBuildAction.run();
return result;
}
构建步骤深度总结(6.5.x 源码级):
| 步骤 | 操作 | 6.5.x 源码深度解析 |
|---|---|---|
| ① | 处理 ignored requests | 创建空过滤器链 (DefaultSecurityFilterChain 的 filters 列表为空),并打印建议废弃的警告日志。在 FilterChainProxy 匹配到该链时,会走 "no security" 路径。 |
| ② | 构建 SecurityFilterChain | 调用每个 HttpSecurity 的 build(),并提取其中的授权规则。兼容 AuthorizationFilter(6.x 推荐)和 FilterSecurityInterceptor(5.x 遗留)两种授权过滤器。 |
| ③ | 构建 PrivilegeEvaluator | 6.x 核心机制 :将上述收集到的所有路由和授权规则,反向构建成一个全局 AuthorizationManager,用于支撑 JSP <sec:authorize url="/admin"> 等视图层 URL 鉴权标签。 |
| ④ | 创建 FilterChainProxy | 注入所有构建好的 SecurityFilterChain。FilterChainProxy 先于 WebSecurity 设置了默认值:StrictHttpFirewall、HttpStatusRequestRejectedHandler、VirtualFilterChainDecorator。 |
| ⑤ | 配置 HttpFirewall | 若用户定义了 HttpFirewall Bean 则注入;否则 FilterChainProxy 保留自身默认的 StrictHttpFirewall。 |
| ⑥ | 配置 RequestRejectedHandler | 若用户定义了 Handler Bean 则注入;若引入 Micrometer 则自动装配组合处理器;否则保留 FilterChainProxy 默认的 HttpStatusRequestRejectedHandler。 |
WebInvocationPrivilegeEvaluator仅用于视图层 URL 鉴权 (如 JSP<sec:authorize url="/admin">标签),不涉及方法级安全。
@PreAuthorize、@PostAuthorize等方法级注解由完全独立的方法安全拦截器体系处理:
- 6.x 推荐:
AuthorizationManagerBeforeMethodInterceptor/AuthorizationManagerAfterMethodInterceptor- 5.x 遗留:
MethodSecurityInterceptor
WebSecurity.performBuild()之所以要反向遍历所有SecurityFilterChain提取授权规则,是为了确保<sec:authorize>标签中的 URL 判断结果与过滤器链的实际授权行为完全一致------这是 Spring Security 6.x 自动集成视图层鉴权的底层原理。
2.4 addAuthorizationManager:反向提取授权规则的实现
这是 performBuild() 中最关键的辅助方法,体现了 6.x 对两种授权过滤器的兼容设计:
java
private boolean addAuthorizationManager(SecurityFilterChain securityFilterChain,
RequestMatcherDelegatingAuthorizationManager.Builder builder) {
boolean mappings = false;
for (Filter filter : securityFilterChain.getFilters()) {
// 兼容 5.x 遗留的 FilterSecurityInterceptor
if (filter instanceof FilterSecurityInterceptor securityInterceptor) {
DefaultWebInvocationPrivilegeEvaluator privilegeEvaluator =
new DefaultWebInvocationPrivilegeEvaluator(securityInterceptor);
privilegeEvaluator.setServletContext(this.servletContext);
AuthorizationManager<RequestAuthorizationContext> authorizationManager =
(authentication, context) -> {
HttpServletRequest request = context.getRequest();
boolean result = privilegeEvaluator.isAllowed(
request.getContextPath(), request.getRequestURI(),
request.getMethod(), authentication.get());
return new AuthorizationDecision(result);
};
builder.add(securityFilterChain::matches, authorizationManager);
mappings = true;
continue;
}
// 6.x 推荐的 AuthorizationFilter
if (filter instanceof AuthorizationFilter authorization) {
AuthorizationManager<HttpServletRequest> authorizationManager =
authorization.getAuthorizationManager();
// 将 HttpServletRequest 类型的授权管理器适配为 RequestAuthorizationContext 类型
builder.add(securityFilterChain::matches,
(authentication, context) -> (AuthorizationDecision) authorizationManager
.authorize(authentication, context.getRequest()));
mappings = true;
}
}
return mappings;
}
设计要点 :
RequestMatcherDelegatingAuthorizationManager.Builder要求AuthorizationManager<RequestAuthorizationContext>类型,而AuthorizationFilter实际持有的是AuthorizationManager<HttpServletRequest>(通过AuthorizeHttpRequestsConfigurer配置)。此处通过 Lambda 适配器完成类型转换,是典型的适配器模式应用。
三、FilterChainProxy.doFilter() 源码拆解
这是所有 HTTP 请求进入 Spring Security 的第一道门槛。在 6.x 版本中,FilterChainProxy 将逻辑拆分为了外层的 doFilter(负责生命周期与异常兜底)和内层的 doFilterInternal(负责防火墙与路由驱动)。
3.1 doFilter 主流程:防重入、异常兜底与上下文清理
java
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// ① 防重入机制:检查 FILTER_APPLIED 标记
// 使用 FilterChainProxy 类全限定名 + ".APPLIED" 作为 attribute key
boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
if (!clearContext) {
// 如果已标记(如 forward/include 场景),直接走内部逻辑,不再清理上下文
doFilterInternal(request, response, chain);
return;
}
try {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
// ② 执行真正的安全过滤逻辑
doFilterInternal(request, response, chain);
}
catch (Exception ex) {
// ③ 使用 ThrowableAnalyzer 解包异常链,定位 RequestRejectedException
Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
Throwable requestRejectedException = this.throwableAnalyzer
.getFirstThrowableOfType(RequestRejectedException.class, causeChain);
if (!(requestRejectedException instanceof RequestRejectedException)) {
throw ex; // 不是防火墙拒绝异常,继续向上抛出(由 Servlet 容器处理)
}
// 交给 RequestRejectedHandler 处理(默认 HttpStatusRequestRejectedHandler → HTTP 400)
this.requestRejectedHandler.handle((HttpServletRequest) request,
(HttpServletResponse) response, (RequestRejectedException) requestRejectedException);
}
finally {
// ④ 核心兜底:清理 SecurityContext,防止线程池复用导致信息串号
this.securityContextHolderStrategy.clearContext();
// 移除防重入标记
request.removeAttribute(FILTER_APPLIED);
}
}
补充说明 :
ThrowableAnalyzer通过反射遍历异常链(getCause()),可以穿透ServletException、InvocationTargetException等包装异常,精准定位到真正的RequestRejectedException。
3.2 doFilterInternal:防火墙校验与路由驱动(双路径分支)
java
private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// ① 防火墙校验:同时包装请求和响应
FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest) request);
HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse) response);
// ② 路由匹配:根据请求 URL 短路匹配对应的过滤器链
List<Filter> filters = getFilters(firewallRequest);
// ───── 分支 A:没有匹配的链 ─────
if (filters == null || filters.isEmpty()) {
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.of(() -> "No security for " + requestLine(firewallRequest)));
}
// 不做任何安全处理,重置防火墙包装,直接调用原始 Servlet FilterChain
firewallRequest.reset();
this.filterChainDecorator.decorate(chain).doFilter(firewallRequest, firewallResponse);
return;
}
// ───── 分支 B:匹配到了安全过滤器链 ─────
if (logger.isDebugEnabled()) {
logger.debug(LogMessage.of(() -> "Securing " + requestLine(firewallRequest)));
}
// ③ 构造 reset Lambda:当所有安全过滤器执行完毕后触发
FilterChain reset = (req, res) -> {
if (logger.isDebugEnabled()) {
logger.debug(LogMessage.of(() -> "Secured " + requestLine(firewallRequest)));
}
firewallRequest.reset(); // 恢复原始的 servletPath 和 pathInfo
chain.doFilter(req, res); // 交还给 Servlet 容器的原始 FilterChain
};
// ④ 装饰并驱动虚拟过滤器链执行
this.filterChainDecorator.decorate(reset, filters).doFilter(firewallRequest, firewallResponse);
}
分支 A(无匹配链) 的场景包括:
- ignored requests(空过滤器链的
DefaultSecurityFilterChain) - 请求 URL 未命中任何 SecurityFilterChain 的
matches()
分支 B(有匹配链) 是正常的安全处理流程。
核心设计解析:
FilterChain reset的设计 :源码中这个reset是一个 Lambda 表达式实现的FilterChain。作为VirtualFilterChain的originalChain(原始链)。当VirtualFilterChain中所有安全过滤器递归执行完毕,调用originalChain.doFilter()时,实际执行的是这个 Lambda------先重置防火墙请求(恢复原始路径信息),再调用 Servlet 容器的原始 FilterChain。
FilterChainDecorator装饰器模式 :6.x 引入的FilterChainDecorator接口由VirtualFilterChainDecorator默认实现。如果引入了 Spring Security Observability(Micrometer),Spring 容器中的ObjectPostProcessor<FilterChainDecorator>会将其替换为带有追踪逻辑的ObservationFilterChainDecorator,从而对每个过滤器的执行进行可观测性埋点。
3.3 getFilters:短路匹配机制
java
// 路由匹配:短路机制,第一个匹配的 SecurityFilterChain 生效
private List<Filter> getFilters(HttpServletRequest request) {
int count = 0;
for (SecurityFilterChain chain : this.filterChains) {
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Trying to match request against %s (%d/%d)",
chain, ++count, this.filterChains.size()));
}
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
关键设计 :
getFilters()返回的是List<Filter>(过滤器列表),不是SecurityFilterChain本身。这是短路匹配的体现:一旦找到第一个匹配的链,立即返回其过滤器列表,不再检查后续的链。因此链的顺序至关重要。
3.4 VirtualFilterChain:递归驱动的虚拟过滤器链
java
// FilterChainProxy 的内部私有类
private static final class VirtualFilterChain implements FilterChain {
private final FilterChain originalChain; // 原始链(即 reset Lambda)
private final List<Filter> additionalFilters; // Spring Security 过滤器列表
private final int size;
private int currentPosition = 0; // 当前位置指针
private VirtualFilterChain(FilterChain chain, List<Filter> additionalFilters) {
this.originalChain = chain;
this.additionalFilters = additionalFilters;
this.size = additionalFilters.size();
}
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if (this.currentPosition == this.size) {
// 所有安全过滤器执行完毕 → 调用 originalChain
// (触发 firewallRequest.reset() → 调用 Servlet 原始 FilterChain)
this.originalChain.doFilter(request, response);
return;
}
this.currentPosition++;
Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);
if (logger.isTraceEnabled()) {
String name = nextFilter.getClass().getSimpleName();
logger.trace(LogMessage.format("Invoking %s (%d/%d)",
name, this.currentPosition, this.size));
}
// 递归调用:将自身作为 FilterChain 传给下一个过滤器
nextFilter.doFilter(request, response, this);
}
}
递归驱动原理 :
VirtualFilterChain通过currentPosition计数器实现递归式过滤器链驱动。每个过滤器在完成自身逻辑后调用chain.doFilter(),实际是回调VirtualFilterChain.doFilter(),使得currentPosition递增并驱动下一个过滤器。这完全符合 Servlet Filter 的责任链模式规范。
3.5 FilterChainDecorator 接口与扩展
java
// FilterChainProxy 内部定义的公开接口
public interface FilterChainDecorator {
// 无安全过滤器时的装饰
default FilterChain decorate(FilterChain original) {
return decorate(original, Collections.emptyList());
}
// 有安全过滤器时的装饰
FilterChain decorate(FilterChain original, List<Filter> filters);
}
// 默认实现
public static final class VirtualFilterChainDecorator implements FilterChainDecorator {
@Override
public FilterChain decorate(FilterChain original) {
return original; // 无过滤器时,直接返回原始链
}
@Override
public FilterChain decorate(FilterChain original, List<Filter> filters) {
return new VirtualFilterChain(original, filters); // 包装为 VirtualFilterChain
}
}
FilterChainDecorator接口和FilterChainDecorator后处理器机制自 6.0 引入,为 Observability 等横切关注点提供了标准的扩展点。
四、HttpFirewall:请求防火墙详解
Spring Security 提供了 HttpFirewall 接口用于在请求进入过滤器链之前进行拦截和包装。它有两个核心接口方法:
java
public interface HttpFirewall {
FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException;
HttpServletResponse getFirewalledResponse(HttpServletResponse response);
}
两个核心实现类:StrictHttpFirewall(6.x 当前默认)和 DefaultHttpFirewall(旧版默认)。
4.1 响应防火墙:FirewalledResponse
两个防火墙实现都通过 getFirewalledResponse() 返回 FirewalledResponse(源码位于 web/src/main/java/.../firewall/FirewalledResponse.java),它负责:
- CRLF 注入防护 :拦截
sendRedirect()、setHeader()、addHeader()和addCookie(),检查参数中是否包含\r(回车)或\n(换行)字符 - Cookie 安全校验 :对 Cookie 的
name、value、path、domain、comment全部进行 CRLF 检测
4.2 默认实现:StrictHttpFirewall(严格模式)
StrictHttpFirewall 是 Spring Security 6.x 的默认防火墙实现 。它的设计哲学是:"与其尝试去清洗(sanitize)一个恶意的 URL,不如直接拒绝它"。
java
@Override
public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException {
// ① 拒绝非法的 HTTP 方法(默认只允许 GET/HEAD/POST/PUT/PATCH/DELETE/OPTIONS)
rejectForbiddenHttpMethod(request);
// ② 拒绝包含黑名单字符的 URL
// 分两次校验:先查 encodedUrlBlocklist(编码后的 URL),再查 decodedUrlBlocklist(解码后的 URL)
rejectedBlocklistedUrls(request);
// ③ 拒绝不受信任的 Host 主机名
rejectedUntrustedHosts(request);
// ④ 拒绝未规范化的 URL(防目录遍历攻击,如包含 ./ 或 ../)
if (!isNormalized(request)) {
throw new RequestRejectedException("The request was rejected because the URL was not normalized.");
}
// ⑤ 拒绝 requestURI 中包含非可打印 ASCII 字符的请求(范围 0x20 ~ 0x7e 之外)
rejectNonPrintableAsciiCharactersInFieldName(request.getRequestURI(), "requestURI");
// ⑥ ★ 返回 StrictFirewalledRequest,将 Header/Parameter 校验延迟到实际调用 getter 时
return new StrictFirewalledRequest(request);
}
4.2.1 StrictFirewalledRequest:延迟校验的设计
StrictFirewalledRequest 是 StrictHttpFirewall 的内部类,继承自 FirewalledRequest。重写了所有 Header 和 Parameter 的 getter 方法,在返回数据之前进行安全校验:
scss
方法级别校验:
- getHeader(name) → validateAllowedHeaderName(name) + validateAllowedHeaderValue(name, value)
- getHeaders(name) → validateAllowedHeaderName(name) + validateAllowedHeaderValue(逐个值)
- getHeaderNames() → validateAllowedHeaderName(逐个 Header 名)
- getDateHeader(name) → validateAllowedHeaderName(name)
- getIntHeader(name) → validateAllowedHeaderName(name)
- getParameter(name) → validateAllowedParameterName(name) + validateAllowedParameterValue(name, value)
- getParameterValues(name) → validateAllowedParameterName(name) + validateAllowedParameterValue(逐个值)
- getParameterNames() → validateAllowedParameterName(逐个参数名)
- getParameterMap() → validateAllowedParameterName + validateAllowedParameterValue(全量遍历)
默认的校验策略:
- Header/Parameter Name :只允许可打印且非 ISO 控制字符(正则
\p{IsAssigned} && [^\p{IsControl}]) - Header Value :允许可打印字符 + Tab(正则
\p{IsAssigned} && [[^\p{IsControl}] || \t]) - Parameter Value :默认不限制(
value -> true)
4.2.2 StrictHttpFirewall 的可配置开关
| 配置方法 | 默认值 | 说明 |
|---|---|---|
setAllowedHttpMethods(Collection) |
7种标准法 | 限制允许的 HTTP 方法 |
setUnsafeAllowAnyHttpMethod(boolean) |
false | 设为 true 则不对 HTTP 方法做任何校验(不推荐) |
setAllowSemicolon(boolean) |
false | 是否允许 URL 中的分号 ;(矩阵变量) |
setAllowUrlEncodedSlash(boolean) |
false | 是否允许 URL 编码的斜杠 %2F / %2f |
setAllowUrlEncodedDoubleSlash(boolean) |
false | 是否允许 URL 编码的双斜杠 %2F%2F 等变体 |
setAllowUrlEncodedPeriod(boolean) |
false | 是否允许 URL 编码的句号 %2E / %2e |
setAllowBackSlash(boolean) |
false | 是否允许反斜杠 \\ 和 %5C / %5c |
setAllowNull(boolean) |
false | 是否允许空字节 \0 和 %00 |
setAllowUrlEncodedPercent(boolean) |
false | 是否允许 URL 编码的百分号 %25(防双重编码) |
setAllowUrlEncodedCarriageReturn(boolean) |
false | 是否允许 URL 编码的回车 %0d / %0D |
setAllowUrlEncodedLineFeed(boolean) |
false | 是否允许 URL 编码的换行 %0a / %0A |
setAllowUrlEncodedLineSeparator(boolean) |
false | 是否允许行分隔符
|
setAllowUrlEncodedParagraphSeparator(boolean) |
false | 是否允许段分隔符
|
setAllowedHostnames(Predicate) |
true |
主机名校验(默认全部允许) |
setAllowedHeaderNames(Predicate) |
非控制字符 | 自定义 Header 名称校验 |
setAllowedHeaderValues(Predicate) |
非控制字符 | 自定义 Header 值校验 |
setAllowedParameterNames(Predicate) |
非控制字符 | 自定义参数名校验 |
setAllowedParameterValues(Predicate) |
true |
自定义参数值校验 |
4.3 旧版实现:DefaultHttpFirewall(宽松/清洗模式)
DefaultHttpFirewall 是早期的默认实现。它的设计哲学是:"尝试清洗和规范化请求,而不是直接拒绝"。
java
@Override
public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException {
// ① ★ 包装请求为 RequestWrapper
// RequestWrapper 在构造时自动剥离路径中的分号(矩阵变量)和连续双斜杠
FirewalledRequest firewalledRequest = new RequestWrapper(request);
// ② 仅校验清洗后的 servletPath 和 pathInfo 是否规范化(防目录遍历)
if (!isNormalized(firewalledRequest.getServletPath()) || !isNormalized(firewalledRequest.getPathInfo())) {
throw new RequestRejectedException("Un-normalized paths are not supported: ...");
}
// ③ 仅校验 requestURI 是否包含编码的斜杠(%2f / %2F)
// 可通过 setAllowUrlEncodedSlash(true) 放开
String requestURI = firewalledRequest.getRequestURI();
if (containsInvalidUrlEncodedSlash(requestURI)) {
throw new RequestRejectedException("The requestURI cannot contain encoded slash. Got " + requestURI);
}
return firewalledRequest;
}
关键区别 :
DefaultHttpFirewall的RequestWrapper会主动修改servletPath和pathInfo(剥离分号后的路径参数和连续双斜杠)。而StrictHttpFirewall的StrictFirewalledRequest不修改路径值,只是拒绝不合规的请求。这体现了两种完全不同的安全哲学。
4.4 两者对比与设计演进
| 对比维度 | DefaultHttpFirewall (旧版) | StrictHttpFirewall (6.x 默认) |
|---|---|---|
| HTTP 方法校验 | ❌ 不校验 | ✅ 默认仅允许 7 种标准方法 |
| URL 分号处理 | ✅ 主动剥离分号及其后内容 | ❌ 直接拒绝包含分号的 URL |
| URL 黑名单字符 | ❌ 仅校验编码斜杠和目录遍历 | ✅ 维护庞大的双重黑名单(编码+解码) |
| Header/Param 校验 | ❌ 不校验 | ✅ 延迟校验,拦截控制字符(防 CRLF 注入) |
| Host 校验 | ❌ 不校验 | ✅ 支持 Predicate 自定义主机名白名单 |
| 双斜杠处理 | ✅ 主动合并连续斜杠 | ❌ 直接拒绝 // |
| 安全性 | 较低,容易被编码绕过 | 极高,多层纵深防御 |
4.5 FirewalledRequest.reset() 的设计
FirewalledRequest 是一个抽象类,定义了 abstract void reset() 方法:
StrictFirewalledRequest.reset():空实现。因为StrictHttpFirewall不修改请求路径,无需恢复。RequestWrapper.reset()(DefaultHttpFirewall使用):通过stripPaths = false标志位恢复原始的未经剥离的servletPath和pathInfo。
FilterChainProxy 在安全过滤完成之后调用 firewallRequest.reset(),确保回到 Servlet 容器原始链时,请求对象的路径信息已恢复到防火墙包装前的状态。
五、SecurityContext 的生命周期管理(双重清理机制)
Spring Security 采用了**"双重清理(Double Clear)"兜底机制**,配合两套独立的
FILTER_APPLIED防重入标记,以确保在 Tomcat 线程池复用场景下,绝对不会发生上下文串号泄漏。
5.1 两套独立的 FILTER_APPLIED 防重入标记
| 组件 | FILTER_APPLIED Key | 作用 |
|---|---|---|
FilterChainProxy |
FilterChainProxy.class.getName() + ".APPLIED" |
防止 FilterChainProxy 被重复执行 |
SecurityContextHolderFilter |
SecurityContextHolderFilter.class.getName() + ".APPLIED" |
防止 SecurityContext 被重复加载/清理 |
两套标记完全独立,这是分层防御理念在请求属性层面的体现。
5.2 第一重清理:SecurityContextHolderFilter(链内常规清理)
在 6.x 中,SecurityContextHolderFilter 负责在请求进入时延迟加载 上下文,并在其 finally 块中进行常规清理:
java
// SecurityContextHolderFilter.doFilter(实际源码)
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
// 独立的 FILTER_APPLIED 防重入检查
if (request.getAttribute(FILTER_APPLIED) != null) {
chain.doFilter(request, response);
return;
}
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
// 延迟加载:loadDeferredContext 返回 Supplier<SecurityContext>
// SecurityContext 只有在首次调用 supplier.get() 时才真正从 Repository 加载
Supplier<SecurityContext> deferredContext = this.securityContextRepository.loadDeferredContext(request);
try {
// 将延迟加载的上下文设置到 SecurityContextHolder
this.securityContextHolderStrategy.setDeferredContext(deferredContext);
chain.doFilter(request, response);
}
finally {
// 第一重清理:正常流程下的上下文回收
this.securityContextHolderStrategy.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}
💡 延迟加载设计(
Supplier<SecurityContext>)的意义:在 6.x 中,
SecurityContextRepository.loadDeferredContext()返回的是Supplier<SecurityContext>而非直接的SecurityContext。这意味着:
- 如果整个请求处理过程中没有任何代码访问
SecurityContextHolder.getContext()(例如访问的是一个完全公开的资源),那么SecurityContext就永远不会被加载。- 避免了不必要的 Session 读取、反序列化等昂贵操作,极大提升了公开资源的吞吐量。
- 配合
SecurityContextHolderStrategy.setDeferredContext(),只有在首次getContext()调用时才触发supplier.get()。
5.3 第二重清理:FilterChainProxy(外层绝对兜底)
正如我们在 FilterChainProxy 源码中看到的,作为整个安全体系的入口,它在最外层的 finally 块中,再次执行了清理操作:
java
// FilterChainProxy.doFilter 最外层 finally
finally {
// 第二重清理:无论过滤器链内部发生什么,上下文必定被清空
this.securityContextHolderStrategy.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
5.4 为什么必须设计"双重清理"?
在 Web 服务器中,线程是被池化复用的。Spring Security 的"双重清理"机制应对以下几种风险场景:
- SecurityContextHolderFilter 不在过滤器链中 :如果用户自定义配置时移除了
SecurityContextHolderFilter(例如使用了仅含自定义过滤器的链),但 SecurityContext 仍可能被其他方式设置(如通过SecurityContextHolderFilter的替代实现),此时必须由FilterChainProxy兜底清理。 - 极端的 JVM 异常 :虽然
try-finally在 Java 语义上保证 finally 块的执行,但在ThreadDeath、OutOfMemoryError等极端场景下,finally可能不完整执行。外层兜底提供了第二道防线。 - 防御性编程 :作为安全框架的"总入口",
FilterChainProxy必须确保无论过滤器链内部发生什么,请求结束前上下文必定清空------这是一种"不信任下游"的防御编程思想。
场景澄清 :如果在
SecurityContextHolderFilter之前 抛出异常(如早期自定义过滤器异常),此时SecurityContext尚未被加载,不存在泄漏风险。真正的双重清理价值在于多层防御:即使某一层清理失效,另一层仍能兜底。
六、完整请求处理流程总结(6.5.x 终极版)
| 阶段 | 组件 | 操作 |
|---|---|---|
| 1 | Servlet 容器 | 收到 HTTP 请求,进入 FilterChain |
| 2 | DelegatingFilterProxy |
查找名为 springSecurityFilterChain 的 Bean,委托给 FilterChainProxy |
| 3 | FilterChainProxy | 检查 FILTER_APPLIED 防重入标记 |
| 4 | FilterChainProxy | doFilterInternal:HttpFirewall.getFirewalledRequest() 校验请求 |
| 5 | FilterChainProxy | doFilterInternal:HttpFirewall.getFirewalledResponse() 包装响应 |
| 6 | FilterChainProxy | getFilters() 路由匹配(短路机制,首个匹配即生效) |
| 7a | FilterChainProxy (分支A) | 无匹配链 → firewallRequest.reset() → 直接调用原始 Servlet FilterChain |
| 7b | VirtualFilterChain (分支B) | FilterChainDecorator.decorate(reset, filters) 构造 VirtualFilterChain |
| 8 | VirtualFilterChain | 递归驱动安全过滤器(currentPosition 指针递增) |
| 9 | SecurityContextHolderFilter |
防重入检查 → loadDeferredContext → setDeferredContext → 驱动后续过滤器 |
| 10 | Security Filters | 依次执行:CsrfFilter、AuthenticationFilter、AuthorizationFilter 等 |
| 11 | VirtualFilterChain | 所有安全过滤器执行完毕 → 调用 originalChain.doFilter() → 触发 reset Lambda |
| 12 | reset Lambda |
firewallRequest.reset() 恢复原始路径 → chain.doFilter() 交还 Servlet 容器 |
| 13 | 业务 Servlet/Controller | 处理实际业务逻辑 |
| 14 | SecurityContextHolderFilter |
finally 块 → 第一重清理 :clearContext() + 移除 FILTER_APPLIED |
| 15 | FilterChainProxy | catch 块:捕获 RequestRejectedException → RequestRejectedHandler.handle() |
| 16 | FilterChainProxy | finally 块 → 第二重清理 :clearContext() + 移除 FILTER_APPLIED |
下一篇预告:将深入拆解 AuthenticationManager 的创建过程------AuthenticationConfiguration 如何自动加载全局配置器,InitializeUserDetailsBeanManagerConfigurer 如何自动创建 DaoAuthenticationProvider,以及 UserDetailsServiceAutoConfiguration 的条件装配逻辑。