Spring Security 作为一个安全框架,它提供了两个基本的功能:1)认证;2)授权。认证表明一个是谁,授权则表示用户可以做什么。在底层实现上,这两个基本功能都是基于 Servlet Filter 来实现的,Filter 是Spring Security 框架的基座。 为了搞清楚其内部的实现原理,本文介绍了 Spring Security 中用到的 Filter 及它们之间的协作,后续再补充认证及授权流程。
整体结构
假定有一个场景,一个服务器基于 Spring Oauth2 实现了认证服务器的功能,其配置如下:
1 | /** |
在上面的代码中,引入了两份配置,一个是引入 Spring Security 本身的配置,另外一份是认证中心的配置。在 Spring Security 配置中,配置了两份的 endponit 的访问权限:
- 忽略的 endponit :
/login.html, /css/**, /js/**, /images/**
, 这些 url 主要是静态资源,不需要授权; - 需要权限认证的 endponit :
/login, /oauth/authorize
, 这两个 endpoint 的访问需要经过 Spring Security 授权(注:/login 是登陆接口,直接放行)。
在认证中心的配置中,需要对 Oauth2 相关的 endpoint 授权:
- Oauth2 endpoint :
/oauth/token, /oauth/token_key, /oauth/check_token
, 这些 endpoint 的访问需要认证服务器进行授权。
注: Oauth2 endpoint 的 endponit 配置在 AuthorizationServerSecurityConfiguration 类中。
针对这些 endpoint, Spring Security 会生成一个系列的 Fileter 去处理,如下图所示:
- 顶层是 DelegatingFilterProxy 类,它是 Spring Security 的入口类,它拦截所有的请求进行安全校验;
- DelegatingFilterProxy 类的业务逻辑委托给名为 springSecurityFilterChain 的 FilterChainProxy 类进行处理;
- FilterChainProxy 类中维护一个 DefaultSecurityFilterChain 的列表,根据不同的 endpont 路由到 DefaultSecurityFilterChain 类中, 路由判断的功能封装在 RequestMatcher 中;
- DefaultSecurityFilterChain 由一系列配置好的 Filter 组成,每一个 Filter 包含一个安全校验功能,根据根据业务需要添加特定功能的 Filter;
- 在忽略的 endponit (不需要认证) 对应的 DefaultSecurityFilterChain 中,Filter 列表为空,表明不做任何检查,一个 endpoint 对应一个空的 DefaultSecurityFilterChain 对象;
- 需要权限认证的 endponit 和 Oauth2 endpoint 则由不同的 Filter 列表进行安全检查,这两个 DefaultSecurityFilterChain 由不同的 HttpSecurity 进行构建;
- 最底层的 Filter 由 FormLoginConfigurer 进行配置,根据不同的业务功能,业务上层可以自由设置。
总上所述:一个忽略的 endponit 生成一个 DefaultSecurityFilterChain,一个 HttpSecurity 对象生成一个 DefaultSecurityFilterChain,在上面的实例中,一共分生成 6 个 DefaultSecurityFilterChain, 即 6 条处理流程。
以 Spring Security 配置为例,它生成的 Filter 列表如下所示:
- WebAsyncManagerIntegrationFilter: 将 SecurityContext 与 WebAsyncManager 整合起来,使得在 Callable 中可以使用 SecurityContext, 从而可以在异步 Servlet 中使用 Spring Security;
- SecurityContextPersistenceFilter: 有两个功能:1)在每次请求前,向 SecurityContextHolder 中添加 SecurityContext 对象,在请求结束后再清空该对象;2)请求结束之后,向 Session 中添加 SecurityContext 对象,以便下次请求使用;
- HeaderWriterFilter: 处于安全的目的,向 http 响应添加一些 Header, 比如 X-Frame-Options, X-XSS-Protection*,X-Content-Type-Options;
- LogoutFilter : 拦截退出请求,注销用户的认证及会议信息,最后跳转到指定页面;
- UsernamePasswordAuthenticationFilter :拦截登录请求,获取用户表单信息,生成 UsernamePasswordAuthenticationToken 对象,调用 AuthenticationManager 对象的 authenticate 认证方法,完成用户的认证流程,最终生成认证结果对象 Authentication,并将该对象添加到 SecurityContextHolder 中;
- RequestCacheAwareFilter:未认证的请求认证成功之后,需要恢复 HttpRequest 的状态,如 Method,Locales,Parameters 等等,RequestCacheAwareFilter 就是用来恢复请求的状态。
- SecurityContextHolderAwareRequestFilter:将 HttpServletRequest 对象扩展为 SecurityContextHolderAwareRequestWrapper 对象,它向 HttpServletRequest 中添加了额外的方法:1)authenticate,允许用户决他们是否已经认证成功或未认证跳转到登录页面;2)login,允许用户进行认证操作;3)logout,允许用户进行登出操作;
- AnonymousAuthenticationFilter:判断 SecurityContextHolder 是否有 Authentication 对象,没有的话则添加一个用户名为 anonymousUser、角色为 ROLE_ANONYMOUS 的 Authentication 对象;
- SessionManagementFilter:加入一些与 Session 相关的操作,如激活 session-fixation 保护机制或检测一个用户多次登陆的问题;
- ExceptionTranslationFilter :处理转译 AccessDeniedException 和 AuthenticationException 异常。检测到 AuthenticationException 异常,重定向到登陆页面,并缓存 RequestCache 对象,便于认证成功之后恢复请求。检测到 AccessDeniedException,如果是匿名用户,则重定向到登陆页面,否则由 AccessDeniedHandlerImpl 对象处理
- FilterSecurityInterceptor:这个过滤器判断认证用户是否具备访问资源(url endpoint)的权限。
在这里面,有几个 Filter 比较重要:
- LogoutFilter: 处理登出请求,清除会话信息,并重定向到指定页面,默认页面是: /login?logout;
- UsernamePasswordAuthenticationFilter : 处理登陆请求,验证用户信息,并最终生成一个 Authentication 对象,里面包含了用户及权限信息;
- RequestCacheAwareFilter: 一个请求如果未认证会跳转到登陆页面,登陆成功为继续之前的请求,而该 Filter 用来恢复之前的请求状态信息,包括 method, header,参数等等信息;
- ExceptionTranslationFilter:用于转译认证异常信息,若未登陆则跳转到登陆页面,若认证失败则跳转到指定页面;
- FilterSecurityInterceptor:用于用户权限判断,判断用户可以访问该资源 (endpoint).
加载 Filter
上文分析了 Spring Security 的 Filter 整体结构,它们是怎么加载到系统中的?如下图为示,展示了与 Filter 加载相关的类。
注册 DelegatingFilterProxy
DelegatingFilterProxy 是 Spring Security Filter 的源头,它是在 SecurityFilterAutoConfiguration 类进行加载,如果项目中引入了 Spring Security 相关的 Jar 包,该配置会自动触发。
1 | false) (proxyBeanMethods = |
- <1> 处该自动配置依赖项目中 AbstractSecurityWebApplicationInitializer, SessionCreationPolicy 类的存在,这两个类是 Spring Security Jar 包中的类,只要引入 Spring Security, 即可触发自动配置;1>
- <2> 处注册 DelegatingFilterProxy,它只是一个代理类,它依赖名为 springSecurityFilterChain 的 Filter, springSecurityFilterChain 才是真正地封装了业务逻辑。2>
加载 springSecurityFilterChain
名为 springSecurityFilterChain Bean 实际上是 FilterChainProxy 类,它由 WebSecurity 创建,其代码如下所示:
1 | false) (proxyBeanMethods = |
- springSecurityFilterChain 对象是在 WebSecurityConfiguration 对象中生成并添加到 Spring 容器中,配置对象则是由 @EnableWebSecurity 注解引入的。开启 @EnableWebSecurity 会全局生成一个 WebSecurity 对象,最后由该对象生成 FilterChainProxy 类;
- FilterChainProxy 的构建逻辑是在 WebSecurity 中进行的,FilterChainProxy 内部维护一个 DefaultSecurityFilterChain 对象列表,而 DefaultSecurityFilterChain 分为两类,一类是忽略的 url 都会生成一个包含 0 个 Filter 的 DefaultSecurityFilterChain 对象,该对象不会做任何拦截操作,等于直接放行,另外一类是安全认证的 Url, 此时它会生成包含一系列 Filter 的 DefaultSecurityFilterChain 对象,具体包括什么 Filter 操作则由 HttpSecurity 配置;
DefaultSecurityFilterChain
DefaultSecurityFilterChain 对象包含两个部分,一是 url 匹配器,匹配的 url 都要执行 Filter 列表的控制逻辑,另外一个就是 Filter 列表,由它组成了 Spring Secuirty 的安全控制逻辑。
1 | public final class DefaultSecurityFilterChain implements SecurityFilterChain { |
构建 DefaultSecurityFilterChain
DefaultSecurityFilterChain 对象由 HttpSecurity 类构建生成,如下代码所示。在代码中,只是简单对 Filter 列表进行排序,传入 RequestMatcher 和 Filter 列表即可。那这两个对象是怎么来的呢?
1 | public final class HttpSecurity extends |
WebSecurityConfigurerAdapter
Spring Security 提供了很多内置化的配置,直接引入 Spring Security 依赖就可以用。如果用户想自定义一些安全配置,则需要继承 WebSecurityConfigurerAdapter 类,传入相应的配置即可,如本文开头的代码所示:
1 | /** |
- <1> 处配置忽略的 url,每一个 url 在 WebSecurity 中都会生成一个 DefaultSecurityFilterChain 对象;1>
- <2> 处配置 HttpSecurity 对象,每一个 WebSecurityConfigurerAdapter 对象都会生成一个 HttpSecurity 对象,一个 HttpSecurity 对象代表对一组 url 进行安全配置,在认证服务器中,会默认生成一个 WebSecurityConfigurerAdapter 对象,对 Oauth2 相关的 endponit 进行安全设置;2>
- <3> 处配置 HttpSecurity 对象的 url 匹配器,表明
/login, /oauth/authorize
受该 HttpSecurity 控制;3> - <4> 处配置表单认证逻辑,该操作会向 HttpSecurity 对象中加入一个 FormLoginConfigurer 对象,该对象最终会向 HttpSecurity 注册 Filter 来实现认证逻辑。4>
- <5> 与 <6> 处则是修改 FormLoginConfigurer 对象的默认参数,在后续的文章中再详细介绍。6>5>
总上所述,我们得出以下结论:
- HttpSecurity 对象的 RequestMatcher 由 requestMatchers 方法设置,默认是拦截所有的请求,用户可以自己设置;
- HttpSecurity 对象中的 Filter 列表由一系列的配置对象生成,而每一个配置对象代表了一个安全操作,一个安全操作则对应了一个或多个 Filter。
添加 Filter
HttpSecurity 对象中的 Filter 列表由一系列的配置对象生成,以 FormLoginConfigurer 对象为例,它为向 HttpSecurity 对象中添加 UsernamePasswordAuthenticationFilter 对象,如代码如下所示:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>> extends
AbstractAuthenticationFilterConfigurer<H, FormLoginConfigurer<H>, UsernamePasswordAuthenticationFilter> {
/**
* Creates a new instance
* @see HttpSecurity#formLogin()
*/
public FormLoginConfigurer() {
// <1> 调用父类方法,设置 authFilter 为 UsernamePasswordAuthenticationFilter 对象
super(new UsernamePasswordAuthenticationFilter(), null);
usernameParameter("username");
passwordParameter("password");
}
public void configure(B http) throws Exception {
// <2> 初始化 UsernamePasswordAuthenticationFilter
authFilter.setAuthenticationManager(http
.getSharedObject(AuthenticationManager.class));
authFilter.setAuthenticationSuccessHandler(successHandler);
authFilter.setAuthenticationFailureHandler(failureHandler);
if (authenticationDetailsSource != null) {
authFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
}
SessionAuthenticationStrategy sessionAuthenticationStrategy = http
.getSharedObject(SessionAuthenticationStrategy.class);
if (sessionAuthenticationStrategy != null) {
authFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
}
RememberMeServices rememberMeServices = http
.getSharedObject(RememberMeServices.class);
if (rememberMeServices != null) {
authFilter.setRememberMeServices(rememberMeServices);
}
// <3> 向 HttpSecurity 添加 UsernamePasswordAuthenticationFilter
F filter = postProcess(authFilter);
http.addFilter(filter);
}
// ...
}
通过分析 FormLoginConfigurer 代码可知,通过其 configure 方法向 HttpSecurity 对象中添加了 UsernamePasswordAuthenticationFilter 过滤器。Spring Security 默认添加了很多的过滤器,这些过滤器在 WebSecurityConfigurerAdapter 对象中指定的,如下代码所示:
1 | public abstract class WebSecurityConfigurerAdapter implements |
说明:
FilterSecurityInterceptor 过滤器由 authorizeRequests()
方法引入。
小结
总上所述,我们可以得到以下结论:
- DelegatingFilterProxy 作为一切 Filter 的源头,它代理了 FilterChainProxy 类;
- FilterChainProxy 由 WebSecurity 对象构建生成,其配置对象是 WebSecurityConfigurerAdapter, 通过它我们可以自定义安全策略;
- DefaultSecurityFilterChain 是一组 Filter 组合,一般由 HttpSecurity 对象构建生成;
- DefaultSecurityFilterChain 每一个 Filter 可以通过一系列的 AbstractHttpConfigurer 对象生成,每一个 AbstractHttpConfigurer 对象代表了一个或多个 Filter.
整体流程
上文分析了 Spring Security 的整体结构及 Filter 的加载过程,这个章节我们将继续分析这些 Filter 如何协作来完成一个 endpoit 的安全检查工作。一个未认证的 oauth/authorize 接口请求需要经过如下图的流程,为了方便理解,这里省略了不重要的步骤,只留下了关键的环节。
oauth/authorize
被 Spring Security 拦截,路由对应的 DefaultSecurityFilterChain 中,被 FilterSecurityInterceptor 处理,此时用户未登陆,抛出 AuthenticationException;- AuthenticationException 被 ExceptionTranslationFilter 捕获,在这里有两个工作:1) 将请求的信息封装成 RequestCache 对象,并保存到 Session, 以便登陆成功之后进行恢复;2)重定向到指定的登陆页面;
- 重定向到登陆页面
/login.html
, 用户输入用户名及密码,提交给登陆处理 endpoinit, 默认为/login, method=POST
; /login, method=POST
请求被 UsernamePasswordAuthenticationFilter 拦截,调用 AuthenticationManager 类中的认证方法完成用户认证工作,并返回 Authentication, 该对象包含了用户名及权限数据,方便后期进行权限判断。认证成功之后,从 Session 中取出 RequestCache 对象,重写到oauth/authorize
;oauth/authorize
重新被请求,RequestCacheAwareFilter 会检查会话中的 endponit 与oauth/authorize
是否匹配,匹配则使用 RequestCache 恢复请求的状态,如 method 方法;- 请求继续,被 FilterSecurityInterceptor 拦截,取出 Session 中的 Authentication 对象,判断已经认证,直接放行;
- 完成 Spring Security 的检查,请求最终到达
oauth/authorize
endpoint.
总结
至此,我们已经完成对 Spring Security Filter 的分析,包括其整体的结构、Filter 的加载流程以及 Filter 之间的协作。整体处理流程分析的粒度还比较粗,后续将对认证及授权两个环节进行单独地分析。
参考: