【Spring Security】 过滤器链加载及执行流程源码分析
【Spring Security】 过滤器链加载及执行流程源码分析
Metadata
title: 【Spring Security】 过滤器链加载及执行流程源码分析
date: 2023-02-01 19:00
tags:
- 行动阶段/完成
- 主题场景/组件
- 笔记空间/KnowladgeSpace/ProgramSpace/ModuleSpace
- 细化主题/Module/SpringSecurity
categories:
- SpringSecurity
keywords:
- SpringSecurity
description: 【Spring Security】 过滤器链加载及执行流程源码分析
总结
1. Filter
Spring Security 的过滤器是基于 Servlet 的 Filter,下图显示了单个 HTTP 请求的处理程序的典型分层。
客户端向应用程序发送一个请求,容器创建一个 FilterChain 包含所有的 Filter 的过滤器链,并且 Servlet 应该 HttpServletRequest 根据请求 URI 的路径处理。由于 Filter 影响下游 Filter 和 Servlet,因此 Filter 调用每个的顺序非常重要。
2. 委托过滤器代理
Spring 提供了一个 Filter 名为 DelegatingFilterProxy 的实现类,它允许在 Servlet 容器的生命周期和 Spring 的 ApplicationContext 生效。 Servlet 容器允许 Filter 使用自己的标准注册,但它不知道 Spring 定义的 Bean。 DelegatingFilterProxy 可以通过标准 Servlet 容器机制注册,但将所有工作委托给实现 Filter Bean。
DelegatingFilterProxy 查找 Bean 的过滤器,然后调用 Bean 的过滤逻辑。
DelegatingFilterProxy 伪代码:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// Lazily get Filter that was registered as a Spring Bean
// For the example in DelegatingFilterProxy delegate is an instance of Bean Filter0
Filter delegate = getFilterBean(someBeanName);
// delegate work to the Spring Bean
delegate.doFilter(request, response);
}
3. 过滤链代理
Spring Security 的 Servlet 支持包含在 FilterChainProxy. FilterChainProxy 是 FilterSpring Security 提供的特殊功能,它允许 Filter 通过 SecurityFilterChain. 由于 FilterChainProxy 是 Bean,它通常被包装在 DelegatingFilterProxy 中。
4. 安全过滤器链
SecurityFilterChain 使用 FilterChainProxy 决定 Spring Security 过滤器应该被哪些请求执行。
Security 中的过滤器在 SecurityFilterChain 通常是 Bean,他们是注册 FilterChainProxy 的 DelegatingFilterProxy 委派代理实现的。 FilterChainProxy 为直接向 Servlet 容器或 DelegatingFilterProxy 注册提供了许多优势。
首先,它为所有 Spring Security 的 Servlet 支持提供了一个起点。因此,如果您正在尝试对 Spring Security 的 Servlet 支持进行故障排除,那么在其中添加调试点 FilterChainProxy 是一个很好的起点。
其次,由于 FilterChainProxy 是 Spring Security 使用的核心,它可以执行不被视为可选的任务。例如,它会清除 SecurityContext 以避免内存泄漏。它还应用 Spring SecurityHttpFirewall 来保护应用程序免受某些类型的攻击。
此外,它在确定何时 SecurityFilterChain 应该调用 a 方面提供了更大的灵活性。在 Servlet 容器中,Filter 仅根据 URL 调用 s。但是,FilterChainProxy 可以 HttpServletRequest 通过利用 RequestMatcher 接口根据中的任何内容确定调用。
事实上,FilterChainProxy 可以用来确定 SecurityFilterChain 应该使用哪个。这允许提供不同的一个完全独立的配置切片您的应用程序。
5. Security Filters
Security 多个过滤器通过 SecurityFilterChain API 插入到 FilterChainProxy 中,执行顺序很重要。通常不需要知道 Spring Security 的排序。然而,有时知道顺序是有益的。
以下是 Spring Security Filter 排序的完整列表:
- ChannelProcessingFilter
- WebAsyncManagerIntegrationFilter
- SecurityContextPersistenceFilter
- HeaderWriterFilter
- CorsFilter
- CsrfFilter
- LogoutFilter
- OAuth2AuthorizationRequestRedirectFilter
- Saml2WebSsoAuthenticationRequestFilter
- X509AuthenticationFilter
- AbstractPreAuthenticatedProcessingFilter
- CasAuthenticationFilter
- OAuth2LoginAuthenticationFilter
- Saml2WebSsoAuthenticationFilter
- UsernamePasswordAuthenticationFilter
- OpenIDAuthenticationFilter
- DefaultLoginPageGeneratingFilter
- DefaultLogoutPageGeneratingFilter
- ConcurrentSessionFilter
- DigestAuthenticationFilter
- BearerTokenAuthenticationFilter
- BasicAuthenticationFilter
- RequestCacheAwareFilter
- SecurityContextHolderAwareRequestFilter
- JaasApiIntegrationFilter
- RememberMeAuthenticationFilter
- AnonymousAuthenticationFilter
- OAuth2AuthorizationCodeGrantFilter
- SessionManagementFilter
- ExceptionTranslationFilter
- FilterSecurityInterceptor
- SwitchUserFilter
6. 异常处理
ExceptionTranslationFilter 过滤器会拦截处理 AccessDeniedException 和 AuthenticationException 并添加到 HTTP 响应中。
ExceptionTranslationFilter 作为安全过滤器之一插入到 FilterChainProxy 中。
- 首先,ExceptionTranslationFilter 调用 FilterChain.doFilter(request, response) 进行应用程序的其余部分。
- 如果用户未通过身份验证或者是 AuthenticationException,则启动身份验证。
(1)、该 SecurityContextHolder 中被清除出
(2)、将 HttpServletRequest 被保存在 RequestCache。当用户成功认证时,RequestCache 用于重放原始请求。
(3)、AuthenticationEntryPoint 用于从客户机请求的凭证。例如,它可能会重定向到登录页面或发送 WWW-Authenticate 标头。 - 如果是 AccessDeniedException,则拒绝访问。AccessDeniedHandler 被将被调用。
如果应用程序不抛出 anAccessDeniedException 或 AuthenticationException,ExceptionTranslationFilter 则不执行任何操作。
【Spring Security】 过滤器链加载及执行流程源码分析
Spring Security 正是基于 Filter 对请求进行拦截处理,实现认证授权功能,那么这个过滤器是怎么加载和实现拦截的呢?
1. 过滤器加载
自从有了 Spring Boot 之后,Spring Boot 对于 Spring Security 提供了自动化配置方案,可以使用更少的配置来使用 Spring Security。首先来看下在 Spring Boot 中 Filter 是怎么使用的。
方式有很多,这个只是参考 Security,添加一个过滤器,注入到 IOC 中。
@Component("myFilter")
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("doFilter start");
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("doFilter end");
}
@Override
public void destroy() {
System.out.println("destroy");
}
}
使用 DelegatingFilterProxyRegistrationBean 注册过滤器,DelegatingFilterProxyRegistrationBean 用于在 Servlet 3.0+ 容器中注册 DelegatingFilterProxy(过滤器的委派代理对象)。类似于 {@link ServletContext} 提供的 {@link ServletContext#addFilter(String, Filter)} 功能,但具有 Spring Bean 友好的设计。
@Configuration
public class FilterConfig {
/**
* 过滤器代理注册器
*
* DelegatingFilterProxyRegistrationBean 通过传入的proxyFilter名字,在WebApplicationContext查找该Fillter Bean,
* 并通过DelegatingFilterProxy生成基于该Bean的代理Filter对象
*/
@Bean
public DelegatingFilterProxyRegistrationBean delegatingFilterProxyRegistrationBean() {
DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean("myFilter");
registration.setOrder(100);
registration.addUrlPatterns("/*");
registration.setDispatcherTypes(ASYNC, ERROR, FORWARD, INCLUDE, REQUEST);
return registration;
}
}
DelegatingFilterProxyRegistrationBean 注册成功后,该过滤器就被加载了到了注册器中。
注册器注册了所以的过滤器后,会为每个过滤器生成代理对象 DelegatingFilterProxy,本质上来说 DelegatingFilterProxy 就是一个 Filter,其间接实现了 Filter 接口,但是在 doFilter 中其实调用的从 Spring 容器中获取到的代理 Filter 的实现类。
创建 myFilter 过滤器的代理对象并注册到 IOC 中。
2. 过滤器执行流程
加载完成后,接下来看下过滤器是如何执行的,我们知道过滤器会对请求进行拦截,那么我们的请求会经过一系列的过滤器处理,首先进入的是 OncePerRequestFilter。OncePerRequestFilter 是为了确保一次请求中只通过一次 filter,而不需要重复的执行。当该请求是第一次执行该过滤器,会进入 DelegatingFilterProxy 代理对象中,代理执行过滤器。
Security 过滤器链初始化
Spring Boot 对于 Spring Security 提供了自动化配置方案,可以使用更少的配置来使用 Spring Security。当项目启动后 SecurityFilterAutoConfiguration 类会加载 DelegatingFilterProxyRegistrationBean 注册过滤器。
同样注册器也会创建 DelegatingFilterProxy 过滤器代理对象.
然后会注入 HttpSecurity 对象,httpSecurity 可以理解为 security 的 http 核心配置,存放 Spring Security 中的过滤器链、请求匹配路径等相关认证授权的重要方法。
然后开始创建过滤器链了,是交给 spring boot 自动配置,由 SpringBootWebSecurityConfiguration 类创建注入。
class SpringBootWebSecurityConfiguration {
@Bean
@Order(2147483642)
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
((HttpSecurity)((HttpSecurity)((AuthorizedUrl)http.authorizeRequests().anyRequest()).authenticated().and()).formLogin().and()).httpBasic();
return (SecurityFilterChain)http.build();
}
}
http.build() 方法会执行 addFilter 将所有的拦截器添加到 HttpSecurity 对象的 filters 集合中,一共有 15 个过滤器。
可以看到这些过滤器也采用了代理模式,使用 OrderedFilter 进行代理,并设置了 order 属性。
添加完成后,将这些过滤器再封装为 DefaultSecurityFilterChain,
最后通过 WebSecurityConfiguration 配置加载 springSecurityFilterChain,WebSecurityConfiguration 中维护了 securityFilterChains 属性,会存放过滤器链中所有的过滤器。
springSecurityFilterChain() 方法会最终注入我们的 springSecurityFilterChain。
最后注入的 springSecurityFilterChain 对象结构如下:
执行流程
首先也是进入 OncePerRequestFilter,可以看到当前 FilterChain 中有 springSecurityFilterChain。
然后进入 filterChain.doFilter(request, response) 方法,实际执行的是 FilterChainProxy 对象的 doFilter 方法,然后首先进入 FilterChainProxy 中的 doFilterInternal。
doFilterInternal 方法中会调用 getFilters 方法,会从过滤器链中拿出所有的拦截器。
然后创建一个 VirtualFilterChain 对象,一个虚拟的过滤器链,并执行其中的 doFilter 方法。使用过滤器对当前请求进行层层过滤。
最后进入到 FilterSecurityInterceptor 过滤器中,该过滤器是过滤器链的最后一个过滤器,invoke 方法中,先调用父类的 beforeInvocation 方法,之后调用 filterChain 的 doFilter 方法,之后调用父类的 finallyInvocation 和 afterInvocation。
在 beforeInvocation 中,如果当前的请求没有通过认证,会抛出 Access is denied 异常,这个异常会被 ExceptionTranslationFilter 过滤器处理。如果抛出的异常是 AuthenticationException,则执行方法 sendStartAuthentication 方法。
最终调用 EntryPoint 的 commence 方法,发布异常。