SpringSecurity中用户认证信息的存取过程:
SpringSecurity将登录用户的信息交给SecurityContextHolder进行管理,然后SecurityContextHolder将用户的登录数据从HttpSession中读入,然后存入到SecurityContext中,当请求完成之后,又将登录信息从SecurityContext中取出,放入到HttpSession中去。
SecurityContextHolder:
SecurityContextHolder 中 存 储 的 是 SecurityContext , SecurityContext 中 存 储 的 则 是
Authentication
三者之间的关系图为:
首先在 SecurityContextHolder 中存放的是 SecurityContext,SecurityContextHolder 中定义
了三种不同的数据存储策略,这实际上是一种典型的策略模式:
(1)MODE_THREADLOCAL:这种存放策略是将 SecurityContext 存放在 ThreadLocal
中,大家知道 ThreadLocal 的特点是在哪个线程中存储就要在哪个线程中读取,这其实非常适
合 Web 应用,因为在默认情况下,一个请求无论经过多少 Filter 到达 Servlet,都是由一个线
程来处理的。这也是 SecurityContextHolder 的默认存储策略,这种存储策略意味着如果在具体
的业务处理代码中,开启了子线程,在子线程中去获取登录用户数据,就会获取不到。
(
2)MODE_INHERITABLETHREADLOCAL:这种存储模式适用于多线程环境,如果希
望在子线程中也能够获取到登录用户数据,那么可以使用这种存储模式。
(
3)MODE_GLOBAL:这种存储模式实际上是将数据保存在一个静态变量中,在 Java
Web 开发中,这种模式很少使用到。
Spring Security 中定义了 SecurityContextHolderStrategy 接口用来规范存储策略中的方法,
我们来看一下:
public interface SecurityContextHolderStrategy {
void clearContext();
SecurityContext getContext();
void setContext(SecurityContext context);
SecurityContext createEmptyContext();
}
接口中一共定义了四个方法:
(1)clearContext:该方法用来清除存储的 SecurityContext 对象。
(
2)getContext:该方法用来获取存储的 SecurityContext 对象。
(
3)setContext:该方法用来设置存储的 SecurityContext 对象。
(
4)createEmptyContext:该方法则用来创建一个空的 SecurityContext 对象
在 Spring Security 中,SecurityContextHolderStrategy 接口一共有三个实现类,分别对应了上面所说的SecurityContextHolder的三种
不同的存储策略,如图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/c1223798e5ff7b1a74a028d3d5d6bc98.png)
SecurityContextHolder默认使用的是ThreadLocalSecurityContextHolderStrategy的策略,即将信息存储在ThreadLocal中,但是SpringBoot中每一次请求,就是一个线程,这样在一次请求中如果使用了子线程那么子线程就无法获取到用户登录的信息,但是实际上,不管我们使用了哪种策略,每一次请求我们都能获取到用户的登录信息,这就是 Spring Security 过滤器链中重要的一环——SecurityContextPersistenceFilter的作用了。
SecurityContextPersistenceFilter
整体上来说,SecurityContextPersistenceFilter 主要做两件事情:
(1)当一个请求到来时,从 HttpSession 中获取 SecurityContext 并存入 SecurityContext
Holder 中,这样在同一个请求的后续处理过程中,开发者始终可以通过 SecurityContextHolder
获取到当前登录用户信息。
(2)当一个请求处理完毕时,从 SecurityContextHolder 中获取 SecurityContext 并存入
HttpSession 中(主要针对异步 Servlet),方便下一个请求到来时,再从 HttpSession 中拿出来
使用,同时擦除 SecurityContextHolder 中的登录用户信息。
注 意
注意:
在 SecurityContextPersistenceFilter 过 滤器中, 当 一个请求处理完毕时, 从
SecurityContextHolder 中获取 SecurityContext 存入 HttpSession 中,这一步的操作主要是针对
异步 Servlet。如果不是异步 Servlet,在响应提交时,就会将 SecurityContext 保存到HttpSession
中了,而不会等到在 SecurityContextPersistenceFilter
过滤器中再去存储。
但是SecurityContextPersistenceFilter的存取又是依托于一个另外的——SecurityContextRepository 接口
将 SecurityContext 存入 HttpSession,或者从 HttpSession 中加载数据并转为 Security
Context 对象,这些事情都是由 SecurityContextRepository 接口的实现类完成的,因此这里我们 就先从 SecurityContextRepository 接口开始看起。
首先我们来看一下 SecurityContextRepository 接口的定义:
public interface SecurityContextRepository {
SecurityContext loadContext(HttpRequestResponseHolder holder);
void saveContext(SecurityContext context, HttpServletRequest request,
HttpServletResponse response);
boolean containsContext(HttpServletRequest request);
}
SecurityContextRepository 接口中一共定义了三个方法:
(1)loadContext:这个方法用来加载 SecurityContext 对象出来,对于没有登录的用户,
这里会返回一个空的 SecurityContext 对象,注意空的 SecurityContext 对象是指 SecurityContext
中不存在 Authentication 对象,而不是该方法返回 null。
(2)saveContext:该方法用来保存一个 SecurityContext 对象。
(3)containsContext:该方法可以判断 SecurityContext 对象是否存在。
在 Spring Security 框架中,为 SecurityContextRepository 接口一共提供了三个实现类,如下图所示:
在这三个实现类中,TestSecurityContextRepository 为单元测试提供支持;NullSecurity ContextRepository 实现类中,loadContext 方法总是返回一个空的 SecurityContext 对象, saveContext 方 法未做任何实现, containsContext 方 法 总 是 返 回 false , 所 以 NullSecurityContextRepository 实现类实际上未做 SecurityContext 的存储工作。 在 Spring Security 中默认使用的实现类是 HttpSessionSecurityContextRepository,通过 HttpSessionSecurityContextRepository 实现了将 SecurityContext 存储到 HttpSession 以及从 HttpSession 中加载 SecurityContext 出来。