Spring Security 的过滤器链,跟你的责任链差了一个生产环境的距离
2026/6/9 10:58:20 网站建设 项目流程

Spring Security 的过滤器链,跟你的责任链差了一个生产环境的距离

责任链模式是面试八股里的常客。标准答案:定义一个 Handler 接口,每个 Handler 持有下一个 Handler 的引用,处理完往后传。然后写个例子——请假审批:组长批三天,经理批七天,超过七天找总监。

这的确能跑。但如果你写过 Spring Security 的配置,你会发现它用的过滤器链跟这个"标准责任链"差别大到你怀疑自己学了个假模式。

标准的责任链长什么样

先回忆一下教科书版本:

```java public abstract class Approver { protected Approver next;

public void setNext(Approver next) { this.next = next; } public abstract void handle(Request request);

}

public class Manager extends Approver { @Override public void handle(Request request) { if (request.getDays() <= 7) { System.out.println("经理审批通过"); } else if (next != null) { next.handle(request); } } } ```

这个设计有两个硬伤。

第一,链是静态的。你必须在代码里把每个节点的next设好,运行时改不了顺序。春节来了想说"总监审批下放到经理"?对不起,代码要改。

第二,节点自己决定要不要往后传。Manager里写了next.handle(request),如果某个实现忘了调 next,整条链就断了。责任链的完整性依赖每个节点的"自觉",这在多人协作的项目里是定时炸弹。

Spring Security 的 VirtualFilterChain:谁才是链的主人

Spring Security 的过滤器链不是节点串联——是一个独立的链对象控制所有节点的执行顺序

```java // SecurityFilterChain 的实现 public final class VirtualFilterChain implements FilterChain { private final FilterChain originalChain; private final ListadditionalFilters; private final int size; private int currentPosition = 0;

public VirtualFilterChain(FilterChain originalChain, List<Filter> additionalFilters) { this.originalChain = originalChain; this.additionalFilters = additionalFilters; this.size = additionalFilters.size(); } @Override public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { if (currentPosition == size) { // 所有 Security Filter 执行完了,交给原始链(最终到达 Servlet) originalChain.doFilter(request, response); return; } currentPosition++; Filter nextFilter = additionalFilters.get(currentPosition - 1); nextFilter.doFilter(request, response, this); }

} ```

跟标准责任链的区别:

| | 标准责任链 | Spring Security VirtualFilterChain | |---|---|---| | 链的控制权 | 分散在每个节点 | 集中在 VirtualFilterChain | | 链的顺序 | 代码硬编码 | 通过配置(SecurityConfig)动态决定 | | 节点的职责 | 自己决定要不要传 | 只管自己的逻辑,传不传由链控制 | | 跳过节点 | 不支持 | 通过 RequestMatcher 控制哪些 URL 走哪些过滤器 | | 链的创建 | 代码里 new 出来 | IoC 容器管理,声明式配置 |

VirtualFilterChain 用一个计数器currentPosition迭代 List,每个 Filter 收到this(VirtualFilterChain 本身)作为FilterChain参数。Filter 处理完自己的逻辑后调用chain.doFilter()——此时 VirtualFilterChain 的doFilter又执行一次,currentPosition++,取下一个 Filter。

控制权反转了。Filter 不持有下一个 Filter 的引用,VirtualFilterChain 持有所有 Filter 并决定执行顺序。Filter 只管"我要做什么",VirtualFilterChain 管"按什么顺序做"。

你以为只是顺序问题?还支持跳过

Spring Security 的过滤器链还有一个标准责任链做不到的事:针对不同 URL 使用不同的过滤器组合。

java @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/admin/**").hasRole("ADMIN") .requestMatchers("/api/public/**").permitAll() .anyRequest().authenticated() ) .formLogin(Customizer.withDefaults()); return http.build(); }

/api/public/**不需要走UsernamePasswordAuthenticationFilter。Spring Security 怎么做到的?不是写个 if 判断在 Filter 里跳过——是RequestMatcher在链的构建阶段就决定了哪些 URL 走哪些 Filter。

这意味着过滤器链在创建的时候就"瘦身"了,不是运行时在每个 Filter 里检查是否需要跳过。对于高并发场景,这省了大量无意义的判断开销。

SecurityContextHolder:ThreadLocal 模式的精准应用

过滤器链之外,Spring Security 还藏了一个经典模式——SecurityContextHolder用 ThreadLocal 存储当前请求的安全上下文:

```java public class SecurityContextHolder { private static final ThreadLocal contextHolder = new ThreadLocal<>();

public static SecurityContext getContext() { SecurityContext ctx = contextHolder.get(); if (ctx == null) { ctx = createEmptyContext(); contextHolder.set(ctx); } return ctx; }

} ```

在一个请求的生命周期里,Filter 链校验通过后把认证信息设进去,Controller 层直接拿:

java Authentication auth = SecurityContextHolder.getContext() .getAuthentication();

不需要把Authentication对象作为参数一路从 Filter 传到 Controller 再到 Service——ThreadLocal 让它在当前线程的任何地方都能拿到。这就是 ThreadLocal 模式的精髓:避免参数透传污染方法签名。

但 ThreadLocal 有个坑:线程池复用场景下,上一个请求的 ThreadLocal 可能残留到下一个请求。Spring Security 的SecurityContextPersistenceFilter在请求结束后显式清理:

java finally { SecurityContextHolder.clearContext(); }

不清理会怎样?用户 A 登出后,同一个线程被分配给用户 B 的请求,用户 B 拿到了用户 A 的认证信息——严重的安全漏洞。这就是为什么模式要用对地方:ThreadLocal 能解决参数透传,但你必须管好生命周期。

动手写一个支持动态编排的责任链

理解了 VirtualFilterChain 的思路,你自己也能写一个支持动态编排的责任链。假设你要做一个请求限流框架:

```java public interface RateLimitHandler { boolean handle(Request request); }

public class RateLimitChain { private final List handlers;

public RateLimitChain(List<RateLimitHandler> handlers) { this.handlers = handlers; } public boolean execute(Request request) { for (RateLimitHandler handler : handlers) { if (!handler.handle(request)) { return false; // 任意一个拦截就拒绝 } } return true; // 全部通过 }

} ```

调用方通过构造参数注入处理器列表,顺序由列表决定。需要调顺序?改列表顺序,不改代码。需要加一个处理器?add 到列表里。

相比于传统的 Handler 链式持有 next 引用,这种"集中调度"的方式让链的构建逻辑和控制逻辑完全分离。链的构建是配置层的事(YAML、注解、代码配置),链的执行是运行时的事——两者不再耦合。

回到你的代码

说实话,大部分项目里你用不到 Spring Security 级别的过滤器链设计。但有一条是普适的:不要让链上的节点自己决定要不要往后传。把"链的控制权"从节点手里收回来,交给一个中心化的链对象。

这跟用不用 Spring Security 无关——哪怕你现在写的只是审批流、数据校验管道、消息拦截器链,这条原则也适用。

写设计模式的代码不难,难的是看到一段代码时认出"它在实现什么模式"以及"它在什么地方和标准模式不一样"。Spring Security 的过滤器链就是一个典型的变形——骨架是责任链,但控制权集中化、支持动态编排、与 IoC 容器深度集成。你读懂了这种变形,才算真正理解了责任链。


做了个用卡皮巴拉漫画讲设计模式的小程序「爪爪代码冒险记」。Spring Security 这些"标准模式的变形"在里面用关卡的方式呈现——先让你写标准责任链,再改造出 VirtualFilterChain 的效果。如果你觉得这种从源码反推模式的思路有用,搜一下试试。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询