springsecurity

认证授权 2 Spring Security + JWT (Sp

2021-05-04  本文已影响0人  _River_
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 项目模块下  
上一篇 下一篇

猜你喜欢

热点阅读