bigywater.tech
Spring · 102

FilterChainProxy:安全过滤器链的顺序,多条链怎么匹配

安全过滤器链到底有哪些过滤器、什么顺序?多条链怎么选?为什么 API 应该返回 401 却变成跳转登录页?ignoring 和 permitAll 差在哪?这篇在 Spring Boot 4.0.8、Spring Security 7 上直接打印 FilterChainProxy,再发请求验证。

16 个
什么都不配时默认安全过滤器链的过滤器个数
401 → 302
多链时,没放行 /error,期望 401 的 /api/x 变成跳转到 /login
0 个
web.ignoring() 的路径在 FilterChainProxy 里的过滤器个数

默认链

实测 · 本机执行输出
  -- 默认(没有自己写 SecurityFilterChain):
    Servlet 容器里登记的 DelegatingFilterProxy:name=springSecurityFilterChain,order=-100
    FilterChainProxy 里有 1 条 SecurityFilterChain
      链 1:DefaultSecurityFilterChain,匹配规则 = any request,过滤器 16 个
           1. DisableEncodeUrlFilter
           2. WebAsyncManagerIntegrationFilter
           3. SecurityContextHolderFilter
           4. HeaderWriterFilter
           5. CsrfFilter
           6. LogoutFilter
           7. UsernamePasswordAuthenticationFilter
           8. DefaultResourcesFilter
           9. DefaultLoginPageGeneratingFilter
          10. DefaultLogoutPageGeneratingFilter
          11. BasicAuthenticationFilter
          12. RequestCacheAwareFilter
          13. SecurityContextHolderAwareRequestFilter
          14. AnonymousAuthenticationFilter
          15. ExceptionTranslationFilter
          16. AuthorizationFilter
      URL /api/x 实际会经过的过滤器 16 个,第一个 DisableEncodeUrlFilter,含 BasicAuthenticationFilter=true,含 UsernamePasswordAuthenticationFilter=true
      URL /other 实际会经过的过滤器 16 个,第一个 DisableEncodeUrlFilter,含 BasicAuthenticationFilter=true,含 UsernamePasswordAuthenticationFilter=true
      URL /open/x 实际会经过的过滤器 16 个,第一个 DisableEncodeUrlFilter,含 BasicAuthenticationFilter=true,含 UsernamePasswordAuthenticationFilter=true
      URL /ignored/x 实际会经过的过滤器 16 个,第一个 DisableEncodeUrlFilter,含 BasicAuthenticationFilter=true,含 UsernamePasswordAuthenticationFilter=true
      URL /permitted/x 实际会经过的过滤器 16 个,第一个 DisableEncodeUrlFilter,含 BasicAuthenticationFilter=true,含 UsernamePasswordAuthenticationFilter=true
    GET /x → HTTP 401,WWW-Authenticate Basic realm="Realm",安全相关响应头 ['x-content-type-options', 'x-frame-options', 'cache-control']
    GET /x (带 Basic u:p) → HTTP 200,安全相关响应头 ['x-content-type-options', 'x-frame-options', 'cache-control']

一条 any request 链,16 个过滤器;入口是 order=-100 的 DelegatingFilterProxy。

多条链

实测 · 本机执行输出
  -- 链 1 = securityMatcher(/api/**) @Order(1);链 2 = 兜底 @Order(2):
    Servlet 容器里登记的 DelegatingFilterProxy:name=springSecurityFilterChain,order=-100
    FilterChainProxy 里有 2 条 SecurityFilterChain
      链 1:DefaultSecurityFilterChain,匹配规则 = Or [PathPattern [/api/**]],过滤器 11 个
           1. DisableEncodeUrlFilter
           2. WebAsyncManagerIntegrationFilter
           3. SecurityContextHolderFilter
           4. HeaderWriterFilter
           5. LogoutFilter
           6. BasicAuthenticationFilter
           7. RequestCacheAwareFilter
           8. SecurityContextHolderAwareRequestFilter
           9. AnonymousAuthenticationFilter
          10. ExceptionTranslationFilter
          11. AuthorizationFilter
      链 2:DefaultSecurityFilterChain,匹配规则 = any request,过滤器 15 个
      URL /api/x 实际会经过的过滤器 11 个,第一个 DisableEncodeUrlFilter,含 BasicAuthenticationFilter=true,含 UsernamePasswordAuthenticationFilter=false
      URL /other 实际会经过的过滤器 15 个,第一个 DisableEncodeUrlFilter,含 BasicAuthenticationFilter=false,含 UsernamePasswordAuthenticationFilter=true
      URL /open/x 实际会经过的过滤器 15 个,第一个 DisableEncodeUrlFilter,含 BasicAuthenticationFilter=false,含 UsernamePasswordAuthenticationFilter=true
      URL /ignored/x 实际会经过的过滤器 15 个,第一个 DisableEncodeUrlFilter,含 BasicAuthenticationFilter=false,含 UsernamePasswordAuthenticationFilter=true
      URL /permitted/x 实际会经过的过滤器 15 个,第一个 DisableEncodeUrlFilter,含 BasicAuthenticationFilter=false,含 UsernamePasswordAuthenticationFilter=true
    GET /api/x → HTTP 302,Location http://127.0.0.1:9602/login;jsessionid=C570D1B25684EF51ADF744797A9BE2C1,WWW-Authenticate Basic realm="Realm",安全相关响应头 ['x-content-type-options', 'x-frame-options', 'cache-control']
    GET /api/x (带 Basic u:p) → HTTP 200,安全相关响应头 ['x-content-type-options', 'x-frame-options', 'cache-control']
    GET /other → HTTP 302,Location http://127.0.0.1:9602/login,安全相关响应头 ['x-content-type-options', 'x-frame-options', 'cache-control']
    GET /open/x → HTTP 200,安全相关响应头 ['x-content-type-options', 'x-frame-options', 'cache-control']
  -- 链 1 = securityMatcher(/api/**);链 2 里多了 /error 的 permitAll:
    Servlet 容器里登记的 DelegatingFilterProxy:name=springSecurityFilterChain,order=-100
    FilterChainProxy 里有 2 条 SecurityFilterChain
      链 1:DefaultSecurityFilterChain,匹配规则 = Or [PathPattern [/api/**]],过滤器 11 个
           1. DisableEncodeUrlFilter
           2. WebAsyncManagerIntegrationFilter
           3. SecurityContextHolderFilter
           4. HeaderWriterFilter
           5. LogoutFilter
           6. BasicAuthenticationFilter
           7. RequestCacheAwareFilter
           8. SecurityContextHolderAwareRequestFilter
           9. AnonymousAuthenticationFilter
          10. ExceptionTranslationFilter
          11. AuthorizationFilter
      链 2:DefaultSecurityFilterChain,匹配规则 = any request,过滤器 15 个
      URL /api/x 实际会经过的过滤器 11 个,第一个 DisableEncodeUrlFilter,含 BasicAuthenticationFilter=true,含 UsernamePasswordAuthenticationFilter=false
      URL /other 实际会经过的过滤器 15 个,第一个 DisableEncodeUrlFilter,含 BasicAuthenticationFilter=false,含 UsernamePasswordAuthenticationFilter=true
      URL /open/x 实际会经过的过滤器 15 个,第一个 DisableEncodeUrlFilter,含 BasicAuthenticationFilter=false,含 UsernamePasswordAuthenticationFilter=true
      URL /ignored/x 实际会经过的过滤器 15 个,第一个 DisableEncodeUrlFilter,含 BasicAuthenticationFilter=false,含 UsernamePasswordAuthenticationFilter=true
      URL /permitted/x 实际会经过的过滤器 15 个,第一个 DisableEncodeUrlFilter,含 BasicAuthenticationFilter=false,含 UsernamePasswordAuthenticationFilter=true
    GET /api/x → HTTP 401,WWW-Authenticate Basic realm="Realm",安全相关响应头 ['x-content-type-options', 'x-frame-options', 'cache-control']
    GET /api/x (带 Basic u:p) → HTTP 200,安全相关响应头 ['x-content-type-options', 'x-frame-options', 'cache-control']

只选第一条匹配的链;/error 的错误派发要在兜底链里放行,否则 401 会变成 302。

顺序写反

实测 · 本机执行输出
  -- 链 1 = 兜底 @Order(1);链 2 = securityMatcher(/api/**) @Order(2):
    应用启动失败,进程退出;日志里的原因:Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#7ea2412c' defined in class path resource [org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.class]: Failed to instantiate [jakarta.servlet.Filter]: Factory method 'springSecurityFilterChain' threw exception with message: A filter chain that matches any request [DefaultSecurityFilterChain defined as 'web' in [class path resource [demo/CfgMis.class]] matching [any request] and having filters [DisableEncodeUrl, WebAsyncManagerIntegration, SecurityContextHolder, HeaderWriter, Csrf, Logout, UsernamePasswordAuthentication, DefaultResources, DefaultLoginPageGenerating, DefaultLogoutPageGenerating, RequestCacheAware, SecurityContextHolderAwareRequest, AnonymousAuthentication, ExceptionTranslation, Authorization]] has already been configured, which means that this filter chain [DefaultSecurityFilterChain defined as 'api' in [class path resource [demo/CfgMis.class]] matching [Or [PathPattern [/api/**]]] and having filters [DisableEncodeUrl, WebAsyncManagerIntegration, SecurityContextHolder, HeaderWriter, Csrf, Logout, BasicAuthentication, RequestCacheAware, SecurityContextHolderAwareRequest, AnonymousAuthentication, ExceptionTranslation, Authorization]] will never get invoked. Please use `HttpSecurity#securityMatcher` to ensure that there is only one filter chain configured for 'any request' and that the 'any request' filter chain is published last. | Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [jakarta.servlet.Filter]: Factory method 'springSecurityFilterChain' threw exception with message: A filter chain that matches any request [DefaultSecurityFilterChain defined as 'web' in [class path resource [demo/CfgMis.class]] matching [any request] and having filters [DisableEncodeUrl, WebAsyncManagerIntegration, SecurityContextHolder, HeaderWriter, Csrf, Logout, UsernamePasswordAuthentication, DefaultResources, DefaultLoginPageGenerating, DefaultLogoutPageGenerating, RequestCacheAware, SecurityContextHolderAwareRequest, AnonymousAuthentication, ExceptionTranslation, Authorization]] has already been configured, which means that this filter chain [DefaultSecurityFilterChain defined as 'api' in [class path resource [demo/CfgMis.class]] matching [Or [PathPattern [/api/**]]] and having filters [DisableEncodeUrl, WebAsyncManagerIntegration, SecurityContextHolder, HeaderWriter, Csrf, Logout, BasicAuthentication, RequestCacheAware, SecurityContextHolderAwareRequest, AnonymousAuthentication, ExceptionTranslation, Authorization]] will never get invoked. Please use `HttpSecurity#securityMatcher` to ensure that there is only one filter chain configured for 'any request' and that the 'any request' filter chain is published last.

兜底链放前面会在启动时失败并指出被遮蔽的链。

ignoring 与 permitAll

实测 · 本机执行输出
  -- /ignored/** 走 web.ignoring(),/permitted/** 走 permitAll():
    Servlet 容器里登记的 DelegatingFilterProxy:name=springSecurityFilterChain,order=-100
    FilterChainProxy 里有 2 条 SecurityFilterChain
      链 1:DefaultSecurityFilterChain,匹配规则 = PathPattern [/ignored/**],过滤器 0 个
      链 2:DefaultSecurityFilterChain,匹配规则 = any request,过滤器 11 个
      URL /api/x 实际会经过的过滤器 11 个,第一个 DisableEncodeUrlFilter,含 BasicAuthenticationFilter=false,含 UsernamePasswordAuthenticationFilter=false
      URL /other 实际会经过的过滤器 11 个,第一个 DisableEncodeUrlFilter,含 BasicAuthenticationFilter=false,含 UsernamePasswordAuthenticationFilter=false
      URL /open/x 实际会经过的过滤器 11 个,第一个 DisableEncodeUrlFilter,含 BasicAuthenticationFilter=false,含 UsernamePasswordAuthenticationFilter=false
      URL /ignored/x 实际会经过的过滤器 0 个(没有任何链匹配,不经过 Spring Security)
      URL /permitted/x 实际会经过的过滤器 11 个,第一个 DisableEncodeUrlFilter,含 BasicAuthenticationFilter=false,含 UsernamePasswordAuthenticationFilter=false
    GET /ignored/x → HTTP 200,安全相关响应头 []
    GET /permitted/x → HTTP 200,安全相关响应头 ['x-content-type-options', 'x-frame-options', 'cache-control']
    GET /secret → HTTP 403,安全相关响应头 ['x-content-type-options', 'x-frame-options', 'cache-control']

ignoring 完全绕过过滤器链,没有安全响应头;permitAll 仍经过链。

分步演示

FilterChainProxy
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动(Spring Security 7 随 spring-boot-starter-security)。一个 @RestController 对所有路径返回 ok;默认用户 u / p;过滤器清单与链的匹配规则取自 FilterChainProxy.getFilterChains(),每个 URL 实际经过的过滤器取自 FilterChainProxy.getFilters(url);HTTP 响应用 Python http.client 取得,不自动跟随重定向
    • 没有验证:Spring Security 7 里 SecurityFilterChain 的更多扩展(如 securityMatchers 与 MVC 匹配器的差别);自定义过滤器 addFilterBefore / addFilterAfter 的位置;ASYNC 与 FORWARD 派发的授权规则(shouldFilterAllDispatcherTypes);Spring Security 对静态资源的默认行为;响应式 WebFlux 的 SecurityWebFilterChain
    京ICP备2026067502号-1京公网安备11010502063487号