Spring Security 的过滤器链,跟你的责任链差了一个生产环境的距离
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, ListFilter 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 收到thisVirtualFilterChain 本身作为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 里检查是否需要跳过。对于高并发场景这省了大量无意义的判断开销。SecurityContextHolderThreadLocal 模式的精准应用过滤器链之外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(ListRateLimitHandler 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 的效果。如果你觉得这种从源码反推模式的思路有用搜一下试试。