Spring Security 6.x 过滤器链SecurityFilterChain是如何工作的?
一、背景:为什么需要 SecurityFilterChain
?
默认的过滤器链:
1. Servlet Filter
的基础作用
在 Java Web 开发中,Servlet 提供了一个 Filter
机制,用于拦截 HTTP 请求和响应。Filter
是一个链式结构(FilterChain
),可以按顺序执行多个过滤器。例如:
- 认证过滤器:检查用户是否登录。
- 鉴权过滤器:检查用户是否有权限访问某个资源。
- 日志过滤器:记录请求信息。
理论上,我们可以直接编写一系列与安全相关的 Filter
(如认证、鉴权等),将它们注册到 Servlet 的 FilterChain
中,就可以实现安全功能。既然如此,为什么还需要 Spring Security 的 SecurityFilterChain
?这正是初学者常见的疑问。
2. 直接使用 Servlet Filter
的局限性
尽管 Servlet Filter
看起来简单直接,但在实际开发中,尤其是复杂的 Spring 应用中,直接使用 Filter
会遇到以下问题:
- Spring 容器与 Servlet 容器的时间差:Servlet 容器(如 Tomcat)启动时,
Filter
会被初始化并注册,而此时 Spring 容器(ApplicationContext
)可能还未完全加载,导致Filter
无法直接获取 Spring 管理的 Bean。 - 代码重复与维护成本:如果每个安全相关的
Filter
都需要手动处理 Spring Bean 的获取,代码会变得冗余,违背依赖注入的原则。 - 灵活性不足:Servlet
Filter
的匹配规则仅限于 URL 模式(通过web.xml
或@WebFilter
配置),无法满足复杂的匹配需求。 - 调试困难:多个
Filter
独立运行,调试时难以统一跟踪安全相关的逻辑。
Spring Security 的 SecurityFilterChain
正是为了解决这些问题而设计的。
二、逐步分析 SecurityFilterChain
的必要性
1. 问题 1:如何在 Filter
中获取 Spring 容器中的 Bean?
问题背景
- Servlet 容器启动时,
Filter
实例会先被创建并注册到FilterChain
中。 - 而 Spring 容器(
ApplicationContext
)的 Bean 加载通常在 Servlet 容器启动之后(通过ContextLoaderListener
或 Spring Boot 的自动配置)。 - 这导致在
Filter
初始化时,Spring 容器中的 Bean(例如认证服务、用户详情服务等)尚未可用,无法直接通过@Autowired
注入。
解决方案:DelegatingFilterProxy
Spring 提供了一个特殊的 Filter
实现类 DelegatingFilterProxy
,用来解决这一问题:
- 作用:
DelegatingFilterProxy
是一个“占位”过滤器,在 Servlet 容器启动时先注册,但不直接执行具体的过滤逻辑。 - 工作原理:
- 在 Servlet 容器启动时,
DelegatingFilterProxy
会被注册到FilterChain
中。 - 当 HTTP 请求到达时,
DelegatingFilterProxy
的doFilter
方法会被调用。 - 在
doFilter
方法中,DelegatingFilterProxy
会从 Spring 容器(ApplicationContext
)中动态查找一个真正的Filter
Bean(通过名称匹配),然后将请求委派给这个 Bean 执行。
- 在 Servlet 容器启动时,
- 代码示例(Spring Boot 自动配置会处理以下内容,无需手动配置):
// 假设 Spring 容器中有一个 Bean 名为 "springSecurityFilterChain"
@Bean
public Filter springSecurityFilterChain() {
// 返回真正的 Filter 实例
return new SomeSecurityFilter();
}
DelegatingFilterProxy
会查找名为springSecurityFilterChain
的 Bean,并委派给它。
好处:通过这种“委派模式”,DelegatingFilterProxy
解决了 Servlet Filter
和 Spring 容器的时间差问题,允许 Filter
访问 Spring 管理的 Bean。
2. 问题 2:是否可以为每个 Filter
都使用 DelegatingFilterProxy
?
问题分析
假设我们为 Spring Security 中的每个安全相关的 Filter
(如认证、鉴权、CSRF 保护等)都创建一个独立的 DelegatingFilterProxy
,会带来以下问题:
- 代码重复:每个
Filter
都需要在doFilter
方法中手动从ApplicationContext
查找对应的 Bean,代码重复,违背了依赖注入(DI)原则。 - 维护成本高:如果有 10 个安全相关的
Filter
,就需要 10 个DelegatingFilterProxy
,增加了开发和维护成本。 - 调试复杂:多个独立的
DelegatingFilterProxy
分布在FilterChain
中,调试时难以统一跟踪。
解决方案:FilterChainProxy
为了解决上述问题,Spring Security 引入了 FilterChainProxy
,作为所有 Spring Security 相关 Filter
的统一入口:
- 作用:
FilterChainProxy
是 Spring Security 的核心过滤器,负责管理所有安全相关的Filter
,并将它们组织成一个逻辑上的过滤器链。 - 工作原理:
- 从 Servlet
FilterChain
的角度看,Spring Security 只注册了一个Filter
,即DelegatingFilterProxy
。 DelegatingFilterProxy
将请求委派给FilterChainProxy
。FilterChainProxy
内部维护一个或多个SecurityFilterChain
实例,根据请求的 URL 或其他条件选择合适的SecurityFilterChain
来处理请求。
- 从 Servlet
代码示例(Spring Security 内部实现):
public class FilterChainProxy implements Filter {
private List<SecurityFilterChain> filterChains;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// 根据请求选择合适的 SecurityFilterChain
SecurityFilterChain matchingChain = findMatchingChain(request);
if (matchingChain != null) {
matchingChain.doFilter(request, response);
} else {
chain.doFilter(request, response); // 继续下一个 Filter
}
}
}
好处:
- 统一入口:所有 Spring Security 的
Filter
都通过FilterChainProxy
管理,简化了调试。例如,可以在FilterChainProxy
的doFilter
方法中设置断点,跟踪所有安全相关的逻辑。 - 减少代码重复:只需要一个
DelegatingFilterProxy
,避免为每个Filter
重复编写查找 Bean 的逻辑。 - 便于扩展:可以在
FilterChainProxy
中添加全局逻辑,例如日志记录、防火墙等。
3. 问题 3:如何在 FilterChainProxy
和 SecurityFilterChain
之间协作?
桥梁作用
FilterChainProxy
作为统一入口,负责将请求分发给合适的SecurityFilterChain
。SecurityFilterChain
是 Spring Security 中更细粒度的过滤器链,包含用户通过HttpSecurity
配置的各种安全规则(如认证、授权、CSRF 保护等)。
执行流程
- HTTP 请求到达 Servlet 的
FilterChain
。 DelegatingFilterProxy
触发,委派给FilterChainProxy
。FilterChainProxy
根据请求(通过RequestMatcher
匹配)选择一个SecurityFilterChain
。SecurityFilterChain
执行用户配置的Filter
列表,例如:UsernamePasswordAuthenticationFilter
(处理表单登录)。FilterSecurityInterceptor
(执行权限检查)。
- 如果请求通过所有
Filter
,则继续执行后续的 Servlet 逻辑(例如调用 Controller)。
附加功能:
- 在
SecurityFilterChain
的前后,Spring Security 可以添加全局处理逻辑,例如:- 防火墙(
HttpFirewall
):防范特定类型的攻击(如路径遍历攻击)。 - 日志记录:记录每个请求的安全处理结果。
- 防火墙(
4. 问题 4:Servlet Filter
的局限性如何解决?
Servlet Filter
的局限性
- 匹配规则单一:Servlet
Filter
只能通过 URL 模式进行匹配(例如/api/**
),无法支持复杂的条件(如请求头、方法类型等)。 - 配置不灵活:无法动态调整匹配规则或组合多个条件。
解决方案:SecurityFilterChain
和 RequestMatcher
Spring Security 的 SecurityFilterChain
通过 RequestMatcher
接口解决了这些问题:
- 作用:
RequestMatcher
是一个接口,用于定义请求匹配规则,Spring Security 提供了多种实现:AntPathRequestMatcher
:基于 Ant 风格的 URL 匹配(例如/public/**
)。RegexRequestMatcher
:基于正则表达式的 URL 匹配。AndRequestMatcher
/OrRequestMatcher
:组合多个匹配规则。- 自定义
RequestMatcher
:可以实现任意复杂的匹配逻辑。
- 示例:
http
.securityMatcher(new AntPathRequestMatcher("/api/**")) // 只匹配 /api/** 路径
.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated());
- 好处:
- 灵活性:支持复杂的匹配逻辑,例如“仅对 POST 请求的
/admin/**
路径进行认证”。 - 可扩展性:通过自定义
RequestMatcher
,可以实现任意匹配规则。
- 灵活性:支持复杂的匹配逻辑,例如“仅对 POST 请求的
三、SecurityFilterChain
的必要性总结
SecurityFilterChain
并不是简单地替代 Servlet Filter
,而是为了解决直接使用 Filter
的多种问题,提供更强大、灵活和可维护的安全机制。总结其必要性如下:
解决 Spring 容器与 Servlet 容器的时间差:
- 通过
DelegatingFilterProxy
,在Filter
中动态获取 Spring Bean。
- 通过
减少代码重复,提高可维护性:
- 使用
FilterChainProxy
作为统一入口,避免为每个Filter
重复编写查找 Bean 的逻辑。
- 使用
便于调试和扩展:
FilterChainProxy
提供单一入口,便于跟踪和调试安全逻辑。- 可以在
SecurityFilterChain
前后添加全局处理(如防火墙)。
增强灵活性:
- 通过
RequestMatcher
,支持复杂的请求匹配规则,超越了 ServletFilter
的局限性。
- 通过
模块化和可配置性:
- 用户可以通过
HttpSecurity
配置多个SecurityFilterChain
,实现模块化的安全策略(例如 API 和 Web 页面使用不同的安全规则)。
- 用户可以通过
四、实际应用中的例子
假设你有一个 Web 应用,需要以下安全规则:
/public/**
:公开访问。/api/**
:需要 Basic 认证。/admin/**
:需要表单登录和管理员权限。
使用 SecurityFilterChain
,可以这样配置:
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain publicFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/public/**")
.authorizeHttpRequests((requests) -> requests.anyRequest().permitAll());
return http.build();
}
@Bean
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/**")
.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated())
.httpBasic(withDefaults());
return http.build();
}
@Bean
public SecurityFilterChain adminFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/admin/**")
.authorizeHttpRequests((requests) -> requests.anyRequest().hasRole("ADMIN"))
.formLogin(withDefaults());
return http.build();
}
}
- 执行流程:
- 请求到达
FilterChainProxy
。 - 根据
securityMatcher
,选择合适的SecurityFilterChain
:/public/home
->publicFilterChain
-> 直接通过。/api/data
->apiFilterChain
-> 触发 Basic 认证。/admin/dashboard
->adminFilterChain
-> 触发表单登录并检查角色。
- 如果没有匹配的
SecurityFilterChain
,则继续执行后续逻辑。
- 请求到达
五、总结
Spring Security 的 SecurityFilterChain
是对 Servlet Filter
的增强和封装,解决了直接使用 Filter
的多个问题,包括容器时间差、代码重复、调试困难和匹配规则的局限性。通过 DelegatingFilterProxy
、FilterChainProxy
和 SecurityFilterChain
的协作,Spring Security 提供了一个强大、灵活且易于维护的安全框架。
借用网络上的一张图,原博文链接:http://www.fullstackyang.com/springan-quan-kuang-jia-xi-lie-sessionguan-li/