资讯详情 Spring Security 如何注入 Tomcat Filter 链?核心源码解析
📅 2026/10/5 1:34:40
如果你写过 Spring Boot 项目并且引入了 Spring Security十有八九会在启动日志里看到这样一段信息2024-xx-xx 12:00:00.123 INFO 12345 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [org.springframework.security.web.session.ConcurrentSessionFilter..., org.springframework.security.web.context.SecurityContextPersistenceFilter..., ...]看到这里你可能会想这串过滤器到底是从哪儿来的Spring Security 的过滤器是怎么跑到 Tomcat 的 Filter 链里的它能自己决定顺序吗为什么有时候我自定义的 Filter 明明注册了却不生效这篇文章我想用源码跟踪的方式把“Spring Security 注入 Tomcat Filter 链”这条完整链路彻底讲透。整个过程会涉及到 Spring Boot 自动配置、Servlet 容器初始化、DelegatingFilterProxy 代理机制、FilterChainProxy 的二次分发以及 Tomcat 内部 ApplicationFilterChain 的组装方式。内容偏源码但我会尽量用实际操作和类比交代清楚每一步在干什么即使你只是刚接触 Spring Security 不久跟着跑一遍也能看明白。1. 整体设计思路先搞清楚两个“Filter 链”到底谁是谁1.1 一个 Servlet 应用的标准 Filter 注册方式要理解 Spring Security 的注入机制第一步得先把 Servlet 规范里的 Filter 注册方式搞明白。在传统 Java Web 应用里给 Tomcat 注册 Filter 主要有三种方式在web.xml里写filter和filter-mapping使用 Servlet 3.0 的WebFilter注解通过编程式注册也就是ServletContext.addFilter(String filterName, Filter filter)。不管用哪种方式最终都是在ServletContext上挂了一个 Filter 定义。Tomcat 内部会把每个 Filter 封装成一个ApplicationFilterConfig保存到StandardContext.filterDefs里。当请求到达时Tomcat 根据请求路径和 DispatcherType 匹配FilterMap找出需要执行的过滤器按注册顺序组装成一条ApplicationFilterChain然后一次执行。Spring Security 本质上是一大堆 Servlet Filter 的集合。问题在于Spring Security 的过滤器是在 Spring IoC 容器里管理的 Bean并不直接被 Servlet 容器认识。于是它需要一个桥接方案让 Spring 容器里的过滤器能注册进 Tomcat 的 Filter 链。1.2 Spring Security 的“代理注册法”这个桥接方案的核心类叫DelegatingFilterProxy很多人把它称为“代理过滤器”。DelegatingFilterProxy本身是一个普通的、实现了javax.servlet.Filter接口的类。Spring Boot 在启动时会把它作为唯一的 Filter 实例通过ServletContext.addFilter()注册到 Tomcat。也就是说Tomcat 的 Filter 链里那个名为springSecurityFilterChain的过滤项真实对象其实是DelegatingFilterProxy。而真正的安全过滤逻辑藏在 Spring 容器里一个名叫springSecurityFilterChain的 Bean 中这个 Bean 的类型是FilterChainProxy。DelegatingFilterProxy不干活它只负责在请求进来时从 Spring 容器里把FilterChainProxy拿出来把请求转交给它。这种设计带来的好处是什么第一个好处是解耦。DelegatingFilterProxy不依赖 Spring 容器何时初始化完成。因为 Tomcat 的 Filter 注册发生在 Servlet 容器启动阶段而 Spring 容器的 Bean 初始化发生在 ApplicationContext 刷新阶段这两者在时序上是有微妙区别的。DelegatingFilterProxy只在请求真正到达时才去容器里查找委托对象从而避免了“Filter 先注册、Bean 还没准备好”的时序问题。第二个好处是方便 Spring Security 自身的扩展。安全过滤链是不固定的你可以配置任意多个内置安全过滤器也可以插入自定义过滤器。如果直接把这一大串过滤器全部注册到 Tomcat那 Tomcat 的 Filter 链会被塞得乱七八糟你也不好管理。用DelegatingFilterProxy做“门面”Tomcat 只认识一个 Filter真正的过滤链由 Spring Security 内部自由调度。1.3 Spring Boot 与传统 war 包部署的路径差异这里需要说一个很重要的事实Spring Boot 内嵌 Tomcat 和传统 war 包部署到独立 Tomcat虽然最终都是把DelegatingFilterProxy注册进去但触发注册的入口完全不同。传统 war 包部署Tomcat 根据 Servlet 3.0 规范通过ServletContainerInitializer机制加载 Spring 的SpringServletContainerInitializer它会扫描实现了WebApplicationInitializer接口的类。Spring Security 提供的AbstractSecurityWebApplicationInitializer就会在这个环节被调用最终完成DelegatingFilterProxy的注册。Spring Boot 内嵌 Tomcat没有web.xml也不太依赖ServletContainerInitializer扫描。Spring Boot 自己有一套ServletContextInitializer机制它会把实现ServletContextInitializer接口的 Bean 收集起来在 Tomcat 的 Context 初始化时逐个调用。Spring Boot 的自动配置类SecurityFilterAutoConfiguration会注册一个DelegatingFilterProxyRegistrationBean这个 Bean 实现了ServletContextInitializer于是它就成了内嵌 Tomcat 场景下真正执行注册的入口。这两种路径最终都会落到同一个方法ServletContext.addFilter()。2. 启动期源码拆解从自动配置到 addFilter 的完整链路2.1 起点SecurityFilterAutoConfiguration 的内部逻辑我们在 Spring Boot 项目里引入spring-boot-starter-security之后真正触发过滤器注册的自动配置类叫SecurityFilterAutoConfiguration。这个自动配置类来自spring-boot-autoconfigure模块它本身不注册安全过滤器而是注册一个“能被 Servlet 容器识别的注册器”。核心代码如下AutoConfiguration(after SecurityAutoConfiguration.class) ConditionalOnWebApplication(type Type.SERVLET) ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class, SessionCreationPolicy.class }) EnableConfigurationProperties(SecurityProperties.class) Import({ SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class }) public class SecurityFilterAutoConfiguration { private static final String DEFAULT_FILTER_NAME AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME; Bean ConditionalOnBean(name DEFAULT_FILTER_NAME) public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration( SecurityProperties securityProperties) { DelegatingFilterProxyRegistrationBean registration new DelegatingFilterProxyRegistrationBean( DEFAULT_FILTER_NAME); registration.setOrder(securityProperties.getFilter().getOrder()); registration.setDispatcherTypes(getDispatcherTypes(securityProperties)); return registration; } }这里有几个关键点值得展开。第一DEFAULT_FILTER_NAME的常量值是springSecurityFilterChain。这个名字贯穿始终后面你会在日志、调试、配置中反复看到它。第二ConditionalOnBean(name DEFAULT_FILTER_NAME)表示只有当容器中存在名为springSecurityFilterChain的 Bean 时才会注册这个DelegatingFilterProxyRegistrationBean。而springSecurityFilterChain这个 Bean 就是FilterChainProxy它是在你配置SecurityFilterChain的过程中被创建出来的。换句话说你如果没有配置任何安全规则这个代理过滤器根本不会被注册。第三registration.setOrder(securityProperties.getFilter().getOrder())这里的 Order 默认值是SecurityProperties.DEFAULT_FILTER_ORDER也就是-100。这意味着 Spring Security 的过滤器会在很多自定义 Filter 之前执行因为 Spring Boot 的 Filter 排序规则是 Order 越小越靠前。2.2 ServletContextInitializerBeans 如何收集注册器DelegatingFilterProxyRegistrationBean被声明为Bean之后它进入了 Spring 容器。接下来问题变成谁会在合适的时候调用它答案是ServletContextInitializerBeans。这个类职责非常专一从 BeanFactory 中收集所有实现了ServletContextInitializer接口的 Bean。它会做排序、分类然后等待 Tomcat Context 启动时逐项调用。在 Spring Boot 内嵌 Tomcat 的启动流程中ServletWebServerApplicationContext的selfInitialize方法会执行这个收集动作。核心代码如下private void selfInitialize(ServletContext servletContext) throws ServletException { prepareWebApplicationContext(servletContext); registerApplicationScope(servletContext); WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext); for (ServletContextInitializer bean : getServletContextInitializerBeans()) { bean.onStartup(servletContext); } }getServletContextInitializerBeans()返回的集合里就有我们的DelegatingFilterProxyRegistrationBean。为什么它会被收集到因为DelegatingFilterProxyRegistrationBean继承自AbstractFilterRegistrationBean而AbstractFilterRegistrationBean实现了ServletContextInitializer接口。层次关系如下public abstract class AbstractFilterRegistrationBeanT extends Filter implements ServletContextInitializer, BeanNameAware { // ... }这个继承设计是理解整条链路的关键。Spring Boot 没有直接去扫描WebFilter注解而是把“注册器”也做成了 Spring Bean然后通过统一的ServletContextInitializer回调完成注册。2.3 DelegatingFilterProxyRegistrationBean 的 onStartup 到底做了什么AbstractFilterRegistrationBean实现了onStartup方法代码并不复杂Override public void onStartup(ServletContext servletContext) throws ServletException { Filter filter getFilter(); if (!isEnabled()) { return; } String filterName getOrDeduceName(filter); FilterRegistration.Dynamic addedFilter servletContext.addFilter(filterName, filter); if (addedFilter null) { return; } configure(addedFilter); }我来拆解一下这个方法做的事getFilter()从DelegatingFilterProxyRegistrationBean内部拿到要注入的 Filter 实例。这个实例就是DelegatingFilterProxy。getOrDeduceName(filter)推导 Filter 名称。DelegatingFilterProxyRegistrationBean构造时传入了DEFAULT_FILTER_NAME也就是springSecurityFilterChain所以这里拿到的名称就是它。servletContext.addFilter(filterName, filter)是真正的注册动作。这一步会把DelegatingFilterProxy实例和名称关联创建FilterRegistration.Dynamic对象。configure(addedFilter)会设置 URL 匹配模式、DispatcherType、初始化参数等。Spring Boot 默认把安全过滤器映射到/*也就是所有请求都会经过它。到这一步Spring 容器和 Servlet 容器已经完成了第一次握手Tomcat 知道了有一个名为springSecurityFilterChain的过滤器要注册。2.4 Tomcat 侧接收从 addFilter 到 FilterDefservletContext.addFilter()不是一个空操作它背后在 Tomcat 内部做了很多事情。在 Tomcat 中ServletContext的实现类是ApplicationContext。当它调用addFilter时内部会创建一个ApplicationFilterRegistration然后执行context.addFilterDef(filterDef);其中filterDef里包含了过滤器名称、过滤器实例、初始化参数和 Class 等信息。context.addFilterDef会把这个FilterDef放入StandardContext.filterDefs这个 Map 结构中。这里要给不熟悉 Tomcat 的同学补一个背景StandardContext相当于一个 Web 应用在 Tomcat 中的核心对象。它管理着 Servlet、Filter、Listener、Session 等各类资源。FilterDef是 Filter 的静态定义FilterMap是过滤器与 URL 路径的映射关系而ApplicationFilterConfig是运行时对FilterDef的封装在里面完成 Filter 实例的创建和生命周期管理。所以Spring Security 的DelegatingFilterProxy注入 Tomcat 后最终体现为StandardContext.filterDefs里多了一条名为springSecurityFilterChain的条目并且StandardContext.filterMaps里多了一条/*的映射。2.5 传统 war 包部署的另一条注入路径如果你还在用 Spring Boot 的 war 包方式部署到独立 Tomcat那注入路径就变成由AbstractSecurityWebApplicationInitializer负责。这个类实现了 Spring Web 的WebApplicationInitializer接口。Tomcat 启动时会自动扫描ServletContainerInitializer实现类Spring 在 Jar 包中通过 SPI 文件配置了SpringServletContainerInitializer。它会遍历所有实现了WebApplicationInitializer的类并调用其onStartup方法。当你的项目里因为某个配置引入了AbstractSecurityWebApplicationInitializer或子类时它会在onStartup中执行public abstract class AbstractSecurityWebApplicationInitializer implements WebApplicationInitializer { public static final String DEFAULT_FILTER_NAME springSecurityFilterChain; Override public final void onStartup(ServletContext servletContext) { beforeSpringSecurityFilterChain(servletContext); if (this.configurationClasses ! null) { // 注册 Spring 上下文 } insertSpringSecurityFilterChain(servletContext); afterSpringSecurityFilterChain(servletContext); } private void insertSpringSecurityFilterChain(ServletContext servletContext) { String filterName DEFAULT_FILTER_NAME; DelegatingFilterProxy springSecurityFilterChain new DelegatingFilterProxy(filterName); Registration.Dynamic registration servletContext.addFilter(filterName, springSecurityFilterChain); EnumSetDispatcherType dispatcherTypes getSecurityDispatcherTypes(); registration.addMappingForUrlPatterns(dispatcherTypes, false, /*); } }这跟 Spring Boot 内嵌 Tomcat 的注册效果完全一样只不过入口不同。Spring Boot 用DelegatingFilterProxyRegistrationBean传统 war 包用AbstractSecurityWebApplicationInitializer。它们的共同点非常明显都注入一个DelegatingFilterProxy都注册到/*。3. 请求期源码拆解Filter 链如何组装与执行3.1 Tomcat 的 ApplicationFilterChain 是怎么创建的Filter 注册只是静态定义真正的执行要等请求进来。这里我们看一下 Tomcat 如何在请求到达时组装 Filter 链。当一个 HTTP 请求经过 Connector 进入 Engine、Host、Context最终到达目标 Servlet 之前StandardWrapperValve的invoke方法会执行一步关键操作FilterChain filterChain ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);ApplicationFilterFactory.createFilterChain内部会遍历StandardContext的filterMaps根据请求的DispatcherType、请求路径、Filter 的 URL pattern 做匹配。匹配上的 Filter 会通过filterConfig context.findFilterConfig(filterMap.getFilterName());从filterConfigs中取出对应的ApplicationFilterConfig依次加入ApplicationFilterChain的内部数组。这里有个容易踩坑的点StandardContext.filterDefs里存的只是 Filter 的定义真正运行时用的ApplicationFilterConfig是在 Context 启动阶段预生成的或者首次访问时懒加载生成。findFilterConfig查的是filterConfigs如果定义存在而ApplicationFilterConfig没有生成Tomcat 会现场创建一个。最终ApplicationFilterChain持有一个Filter[] filters数组。doFilter方法会从下标 0 开始执行Override public void doFilter(ServletRequest request, ServletResponse response) { internalDoFilter(request, response); } private void internalDoFilter(ServletRequest request, ServletResponse response) { if (pos n) { ApplicationFilterConfig filterConfig filters[pos]; Filter filter filterConfig.getFilter(); filter.doFilter(request, response, this); return; } // 执行到了最后一个过滤器调用目标 Servlet servlet.service(request, response); }这就是经典的责任链模式过滤器依次执行最后一个 Filter 调完chain.doFilter之后目标 Servlet 被调用。Spring Security 的安全过滤逻辑也在这一串普通的 Filter 之中。3.2 DelegatingFilterProxy 如何“偷梁换柱”当ApplicationFilterChain执行到springSecurityFilterChain这个 Filter 时实际执行的是DelegatingFilterProxy实例的doFilter。DelegatingFilterProxy的doFilter方法逻辑非常优雅Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { Filter delegateToUse this.delegate; if (delegateToUse null) { synchronized (this.delegateMonitor) { delegateToUse this.delegate; if (delegateToUse null) { WebApplicationContext wac findWebApplicationContext(); if (wac null) { throw new IllegalStateException(No WebApplicationContext found); } delegateToUse wac.getBean(getTargetBeanName(), Filter.class); this.delegate delegateToUse; } } } delegateToUse.doFilter(request, response, filterChain); }这里做了三件事从ServletContext中获取 Spring 的WebApplicationContext从容器中按 beanNamespringSecurityFilterChain取出实际委托对象调用委托对象的doFilter并且把 Tomcat 的FilterChain继续传下去。大家注意getTargetBeanName()返回的就是构造时传入的springSecurityFilterChain。所以这个 Filter 的名字和委托 Bean 的名字是同一个非常容易迷惑人。在 Spring Security 中springSecurityFilterChain这个 Bean 的类型是FilterChainProxy。它是整个安全框架对外的门面也是所有安全过滤器的总指挥。3.3 FilterChainProxy 里的 VirtualFilterChain 与匹配规则当请求进入FilterChainProxy.doFilter后流程进入 Spring Security 自己的世界。FilterChainProxy内部维护了一个ListSecurityFilterChain也就是你配置的多条安全过滤链。每个SecurityFilterChain包含两个东西一个RequestMatcher用于判断请求是否匹配这条链一个ListFilter也就是具体的过滤器列表。核心方法是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); ListFilter filters getFilters(firewallRequest); if (filters null || filters.size() 0) { // 没有匹配的安全链直接放行 firewallRequest.reset(); chain.doFilter(firewallRequest, firewallResponse); return; } VirtualFilterChain virtualFilterChain new VirtualFilterChain(firewallRequest, chain, filters); virtualFilterChain.doFilter(firewallRequest, firewallResponse); }getFilters会按顺序遍历securityFilterChains找到第一个requestMatcher.matches(request)返回 true 的链。找到后返回该链的过滤器列表。所以 Spring Security 支持多链配置比如/api/**走一条链其他路径走另一条链规则是按声明顺序匹配第一条命中的生效。VirtualFilterChain是FilterChainProxy内部的一个内部类它实现了责任链模式private static final class VirtualFilterChain implements FilterChain { private final ListFilter additionalFilters; private int currentPosition 0; Override public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { if (currentPosition additionalFilters.size()) { originalChain.doFilter(request, response); } else { currentPosition; Filter nextFilter additionalFilters.get(currentPosition - 1); nextFilter.doFilter(request, response, this); } } }到这里你应该能理解 Tomcat 的ApplicationFilterChain和 Spring Security 的VirtualFilterChain之间的关系了它们是两层嵌套的责任链前者是 Servlet 容器层的后者是 Spring Security 内部的。3.4 一个请求进来后的完整链路图文字版我把整个请求流程按顺序列一遍方便你对照源码理解请求进入 Tomcat经过CoyoteAdapter、StandardHostValve等组件StandardWrapperValve调用ApplicationFilterFactory.createFilterChain组装 Tomcat Filter 链Filter 链依次执行。执行到springSecurityFilterChain即DelegatingFilterProxy时请求被转交给 Spring 容器中的FilterChainProxyFilterChainProxy按SecurityFilterChain匹配规则找到对应的安全过滤链生成VirtualFilterChain依次执行 Spring Security 内部过滤器。比如SecurityContextPersistenceFilter、UsernamePasswordAuthenticationFilter、ExceptionTranslationFilter等最后一个安全过滤器执行完VirtualFilterChain调回 Tomcat 的ApplicationFilterChain继续执行后续的 Filter最终调用目标 Servlet也就是 Spring MVC 的DispatcherServlet。如果你在自定义 Filter 中打了断点又在 Spring Security 的过滤器里打了断点就能清楚看到这个两级嵌套的过程。4. 实操经验自己动手打断点验证整条链路说了这么多源码理论你可能已经有点晕了。与其纸上谈兵不如自己在 IDEA 里跑一个最简单的 Spring Boot 项目把断点打到关键类上亲眼看看调用栈长什么样。我把自己经常用的断点位置和排查方法分享给你。4.1 断点位置一启动期注册入口第一个值得打断点的位置是AbstractFilterRegistrationBean.onStartup。你可以在 IDEA 的 Debug 窗口里使用CtrlN搜索AbstractFilterRegistrationBean然后在onStartup方法第一行打上断点。启动项目断点被命中后查看调用栈AbstractFilterRegistrationBean.onStartup ServletContextInitializerBeans$FilterRegistrationBeanContextInitializer.onStartup ... ServletContextInitializerBeans$... ServletWebServerApplicationContext.selfInitialize TomcatStarter.start这个调用栈能直接回答你“Spring Security 到底是在哪一步把自己注册进 Tomcat 的”。如果你顺着调用栈往下点看到TomcatStarter和Tomcat.startInternal说明这个注册发生在 Tomcat 的启动阶段而不是请求阶段。4.2 断点位置二Filter 实例的创建第二个值得打断点的位置是DelegatingFilterProxy的doFilter方法。这个断点会在项目启动后的第一个请求到达时命中。你可以在断点处观察this.delegate字段的值它应该是一个FilterChainProxy实例。这一步能验证“Tomcat 只认识 DelegatingFilterProxy真正干活的是 FilterChainProxy”这个结论。进一步在FilterChainProxy.doFilterInternal里打断点可以看到getFilters(request)返回的过滤器列表到底有哪些过滤器、顺序如何。特别是你在配置了表单登录、HTTP Basic 等多种方式后这个过滤器列表会变得很长你会直观感受到 Spring Security 内部机制是多么“繁重”。4.3 断点位置三Tomcat 侧过滤器链组装如果你想看 Tomcat 是怎么把整个链串起来的可以在ApplicationFilterFactory.createFilterChain打断点。这个方法会在每个请求进来时被调用你可以在这里查看filterMaps的迭代过程。这个方法里有一个循环逐条匹配FilterMap。你会发现springSecurityFilterChain的filterName和 Tomcat 的filterMaps里的条目是一一对应的。这里有一个非常实用的排查思路如果你自定义的 Filter 没有生效可以在createFilterChain方法里看filterMaps里到底有没有你的 Filter 条目如果没有说明你的注册方式有问题而不是执行阶段的问题。4.4 快速看懂启动日志里的 Filter 链列表启动日志中那一段Will secure any request with ...是 Spring Security 内部打印的它代表的是FilterChainProxy里的那条安全过滤链并不是 Tomcat 的全部 Filter 链。如果你想知道完整的 Tomcat Filter 链里有哪些过滤器需要在ApplicationFilterChain.internalDoFilter方法里看filters数组的内容。在调试过程中我建议把过滤器名称、类名列表打印出来for (ApplicationFilterConfig config : filters) { if (config ! null) { System.out.println(Filter: config.getFilterName()); } }这样你能清晰区分哪些 Filter 是 Spring Boot 或者第三方框架注册的哪些是 Spring Security 内部过滤链的一部分。5. 常见问题与踩坑排查5.1 自定义 WebFilter 为什么被 Spring Security 绕过了初学者最容易犯的一个错误是写了一个WebFilter然后配了ServletComponentScan以为它会在 Spring Security 之前执行。结果发现请求直接被拦截或者根本没有走自定义 Filter。原因在于WebFilter注册的是 Servlet 容器原生的 Filter它确实在 Tomcat 的ApplicationFilterChain里。但是它和 Spring Security 的DelegatingFilterProxy的执行顺序取决于注册顺序而不是你代码的物理位置。如果你没有设置Order那DelegatingFilterProxy会以 Order-100注册几乎肯定在你自定义 Filter 之前。所以即使你的自定义 Filter 匹配了路径也会在 Spring Security 的认证拦截之后才轮到它。如果你想让它提前执行需要用FilterRegistrationBean来注册并指定更小的 Order。示例Bean public FilterRegistrationBeanMyFilter myFilterRegistration(MyFilter filter) { FilterRegistrationBeanMyFilter registration new FilterRegistrationBean(filter); registration.addUrlPatterns(/*); registration.setOrder(-200); return registration; }这种做法实际上也是通过DelegatingFilterProxyRegistrationBean同一套机制注册的所以可控性更高。5.2 为什么有时候 Filter 执行了两次如果一个 Filter 在 Tomcat 的ApplicationFilterChain里执行了一次又在 Spring Security 的某个环节执行了一次你会觉得它“执行了两次”。这种情况最典型的是你把同一个过滤器的 Bean 既注册到了 Tomcat又通过HttpSecurity.addFilterBefore/addFilterAfter加到了FilterChainProxy的过滤链里。所以排查方式就是先看清楚这个过滤器到底是在 Tomcat 层被调用还是被 Spring Security 内部调用。从堆栈上区分非常容易Tomcat 层调用栈里会有ApplicationFilterChainSpring Security 内部调用栈里会有VirtualFilterChain。5.3 同一个 Filter 名重复注册会怎样Tomcat 的ServletContext.addFilter有个规则如果同一个 Filter 名称已经注册过再次注册会返回null。Spring Boot 的AbstractFilterRegistrationBean.onStartup对null的处理是直接return不做任何事后补救。这和Spring Boot 2.x之前的版本表现不一样。之前如果 Filter 名称冲突addFilter返回null后会被视为重复注册直接忽略。所以如果你发现某个 Spring Boot 自动配置的 Filter 没有生效先查一下是不是你自己注册了同名的 Filter。我印象里踩过最深的一个坑是某个项目里为了方便统计请求耗时自定义了一个叫requestLoggingFilter的 Filter结果和某个框架内置的 Filter 重名了。因为注册顺序问题框架的 Filter 先注册我的 Filter 后注册时返回null直接被静默忽略了。5.4 怎么控制 Spring Security 过滤器的位置Spring Security 过滤器本身的位置是由SecurityProperties.DEFAULT_FILTER_ORDER决定的默认值是-100。如果你确实需要调整可以在配置文件中指定spring.security.filter.order-200 spring.security.filter.dispatcher-typesASYNC,ERROR,REQUEST这个配置的本质就是修改SecurityFilterAutoConfiguration里registration.setOrder的那个值。你可以把它放在任何位置但除非你有很强的理由一般不建议乱改。因为 Spring Security 的过滤器需要尽早执行以确保后续所有资源访问都经过安全上下文。5.5 传统 war 包部署时 Spring Security 过滤器没生效如果你把 Spring Boot 项目打成 war 包部署到独立 Tomcat并且用了SpringBootServletInitializer那么AbstractSecurityWebApplicationInitializer那条路径可能不会自动触发此时完全依赖 Spring Boot 的ServletContextInitializer机制。如果你的 war 包部署后安全过滤器没生效优先检查pom.xml里是否把spring-boot-starter-tomcat设为provided启动类是否正确继承了SpringBootServletInitializerServletContextInitializer的启动回调是否真的执行了。其中一个环节断掉都会导致过滤链没有注入。6. 关于这套机制的几个实践总结源码走完最后分享几个我个人在实战中的体会。第一理解DelegatingFilterProxy和FilterChainProxy的关系比记住一堆过滤器名称重要得多。很多人看 Spring Security 源码时把所有过滤器类都看了一遍但没搞懂这两层代理导致遇到请求链路问题时定位不到问题在哪一层。其实只要记住Tomcat 层只有DelegatingFilterProxy一个入口安全逻辑全部在FilterChainProxy内部排查时先分清楚是哪一层出了问题。第二遇到过滤器不生效的问题先顺着“注册”这条线排查而不是直接查执行逻辑。你可以用servletContext.addFilter的注册日志、ApplicationFilterFactory.createFilterChain里的 filterMaps 来确认过滤器到底注册成功了没有。绝大多数“过滤器不生效”的问题根源都在注册阶段就失败了。第三善用 IDEA 的调用栈窗口。源码分析不是让你把每个类都背下来而是遇到疑问时能准确打断点、看调用栈、跳到对应源码。我平时排查 Spring Security 的过滤器链问题时基本就是三处断点AbstractFilterRegistrationBean.onStartup、DelegatingFilterProxy.doFilter、FilterChainProxy.doFilterInternal。把这三处断点打上整个链路就非常清晰了。这套机制虽然看起来绕但它其实把一个复杂问题拆解得很干净Servlet 容器只负责统一执行 Filter 链Spring 容器负责管理和编排真实的安全过滤器中间用DelegatingFilterProxy做桥接。搞懂了这一层后面再看 Spring Security 的认证流程、授权流程都会事半功倍。