安全过滤器链到底有哪些过滤器、什么顺序?多条链怎么选?为什么 API 应该返回 401 却变成跳转登录页?ignoring 和 permitAll 差在哪?这篇在 Spring Boot 4.0.8、Spring Security 7 上直接打印 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.兜底链放前面会在启动时失败并指出被遮蔽的链。
-- /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 仍经过链。