1:Spring Security 介绍:
Spring Security 是Spring 全家桶中非常强大的一个用来做身份 验证以及权限控制的框架,
我们可以轻松地扩展它来满足我们当前系统安全性这方面的需求。
流程概述:
1:SecurityConfiguration 继承 WebSecurityConfigurerAdapter 配置 SpringMVC集成了 Spring Security
2:SecurityConfiguration设置 鉴权过滤器(自定义过滤器)
没有对请求进行真正的过滤 只是用于把Token中的重要用户信息存储在SecurityContextHolder中
3:鉴权过滤器 中对Token进行校验,并把校验通过的用户信息存放在 SecurityContextHolder 的 SecurityContext:
4:SecurityContext 的真正存储位置是在ThreadLocal
5: 在SecurityContextPersistenceFilter过滤器把 SecurityContext 存储到 ThreadLocal 中
6:后续在整个项目中可以通过 SecurityContextHolder 获取Token的用户信息
7:在SecurityConfiguration对存储在SecurityContextHolder中的用户信息做判断权限信息的判断
2:WebSecurityConfigurerAdapter
实际上可以理解为Spring Security 是在Spring MVC 这层进行操作的
@EnableWebSecurity 注解使得 SpringMVC 集成了 Spring Security 的 web 安全
支持WebSecurityConfigurerAdapter 重写了其中的特定方法,
用于自定义 Spring Security 配置。
整个 Spring Security 的工作量,其实都是集中在该配置类。
其中重写configure中可以添加逻辑 等等
1:设置拦截路径
2:设置放行路径
3: 可以添加自定义过滤器 (鉴权过滤器)
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
}
}
3:核心组件之SecurityContextHolder
鉴权过滤器 中 对Token进行校验 假如Token是有效的
可以把Token的关键信息 存放到 SecurityContextHolder 中
作用:保留系统当前的安全上下文细节,其中就包括当前使用系统的用户的信息。
安全上下文细节怎么表示?
用SecurityContext对象来表示
每个用户都会有它的上下文,那这个SecurityContext保存在哪里呢?
存储在一个SecurityContextHolder中,整个应用就一个SecurityContextHolder。
SecurityContextHolder存储SecurityContext的方式?
这要考虑到应用场景。
(1)单机系统,即应用从开启到关闭的整个生命周期只有一个用户在使用。由于整个应用只需要保存一个SecurityContext(安全上下文即可)
(2)多用户系统,比如典型的Web系统,整个生命周期可能同时有多个用户在使用。
这时候应用需要保存多个SecurityContext(安全上下文),需要利用ThreadLocal进行保存,
每个线程都可以利用ThreadLocal获取其自己的SecurityContext,及安全上下文。
4:SecurityContextHolder 存储策略 SecurityContextHolderStrategy
SecurityContextHolder利用了一个SecurityContextHolderStrategy(存储策略)进行上下文的存储。
下面为SecurityContestHolderStrategy 接口 与 实现类源码
这个接口提供创建、清空、获取、设置上下文的操作
//SecurityContextHolderStrategy 接口源码
public interface SecurityContextHolderStrategy {
void clearContext();
SecurityContext getContext();
void setContext(SecurityContext context);
SecurityContext createEmptyContext();
}
GlobalSecurityContextHolderStrategy实现类 实现 SecurityContextHolderStrategy
全局的上下文存取策略,只存储一个上下文,对应前面说的单机系统。
ThreadLocalSecurityContextHolderStrategy实现类 实现 SecurityContextHolderStrategy
多用户系统的上下文存取策略 其ThreadLocal内部会用数组来存储多个对象的。
5:SecurityContext 与 ThreadLocal
SecurityContextHolder 中的数据,本质上是保存在 ThreadLocal 中,
ThreadLocal会为每个线程开辟一个存储区域,来存储相应的对象。
ThreadLocal 的的特点是存在它里边的数据,哪个线程存的,哪个线程才能访问到。
这样就带来一个问题,当不同的请求进入到服务端之后,由不同的 thread 去处理,
按理说后面的请求就可能无法获取到登录请求的线程存入的数据,
例如登录请求在线程 A 中将登录用户信息存入 ThreadLocal,
后面的请求来了,在线程 B 中处理,那此时就无法应该是获取到用户的登录信息。
SecurityContext又是什么时候存放到ThreadLocal中?
SecurityContextPersistenceFilter过滤器(SpringSecurity中自带的 优先级非常高的一个过滤器)
在UsernamePasswordAuthenticationFilter之前执行
以下为SecurityContextPersistenceFilter过滤器 核心源码:
每一个请求到达服务端的时候,首先从 session 中找出来 SecurityContext ,
然后设置到 SecurityContextHolder 中去,方便后续使用,当这个请求离开的时候,
SecurityContextHolder 会被清空,SecurityContext 会被放回 session 中,方便下一个请求来的时候获取。
在 doFilter 方法中,它首先会从 repo 中读取一个 SecurityContext 出来,这里的 repo 实际上就是 HttpSessionSecurityContextRepository,
读取 SecurityContext 的操作会进入到 readSecurityContextFromSession 方法中,
可以看到读取的核心方法 Object contextFromSession = httpSession.getAttribute(springSecurityContextKey);,
这里的 springSecurityContextKey 对象的值就是 SPRING_SECURITY_CONTEXT,读取出来的对象最终会被转为一个 SecurityContext 对象。
public class SecurityContextPersistenceFilter extends GenericFilterBean {
public SecurityContextPersistenceFilter(SecurityContextRepository repo) {
this.repo = repo;
}
public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws IOException, ServletException {
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,response);
//请求进入 从Session中获取SecurityContext
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
try {
//请求进入 SecurityContext 存储到 SecurityContextHolder 中
SecurityContextHolder.setContext(contextBeforeChainExecution);
chain.doFilter(holder.getRequest(), holder.getResponse());
}
finally {
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
///请求离开 清空SecurityContextHolder 中 SecurityContext
SecurityContextHolder.clearContext();
//请求离开 SecurityContext 存储到Session 中
repo.saveContext(contextAfterChainExecution, holder.getRequest(),holder.getResponse());
}
}
}
回到问题:
请求A进来 开启线程A 这个时候该线程A获取 SecurityContext信息 存储到线程A的ThreadLocal
同时经过鉴权过滤器的校验 把用户相关信息存储到SecurityContext中
请求B进来 开启线程B 这个时候该线程B获取 SecurityContext 信息 存储到线程B的ThreadLocal
同时经过鉴权过滤器的校验 把用户相关信息存储到SecurityContext中
但是如果在线程A 中 新开一个内部线程B:
使用SecurityContextHolder.getContext().getAuthentication(),这肯定获取不到用户信息
因为新开的内部线程B中并没有经过 SecurityContextPersistenceFilter过滤器
来获取并存放用户信息到Context中
项目连接
请配合项目代码食用效果更佳:
项目地址:
https://github.com/hesuijin/hesuijin-study-project
Git下载地址:
https://github.com.cnpmjs.org/hesuijin/hesuijin-study-project.git
spring-security-jwt-module 项目模块下
网友评论