Spring Security获取用户认证信息的实现流程
作者:一个双子座的Java攻城狮
登录用户数据获取
SecurityContextHolder
Spring Security 会将登录用户数据保存在 Session 中。但是,为了使用方便,Spring Security在此基础上还做了一些改进,其中最主要的一个变化就是线程绑定。当用户登录成功后,Spring Security 会将登录成功的用户信息保存到 SecurityContextHolder 中。
SecurityContextHolder 中的数据保存默认是通过ThreadLocal 来实现的,使用 ThreadLocal 创建的变量只能被当前线程访问,不能被其他线程访问和修改,也就是用户数据和请求线程绑定在一起。当登录请求处理完毕后,Spring Security 会将 SecurityContextHolder 中的数据拿出来保存到 Session 中,同时将 SecurityContexHolder 中的数据清空。以后每当有请求到来时,Spring Security 就会先从 Session 中取出用户登录数据,保存到SecurityContextHolder 中,方便在该请求的后续处理过程中使用,同时在请求结束时将 SecurityContextHolder 中的数据拿出来保存到 Session 中,然后将SecurityContextHolder 中的数据清空。
实际上 SecurityContextHolder 中存储是 SecurityContext,在 SecurityContext 中存储是 Authentication。
这种设计是典型的策略设计模式:
public class SecurityContextHolder { public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL"; public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL"; public static final String MODE_GLOBAL = "MODE_GLOBAL"; private static final String MODE_PRE_INITIALIZED = "MODE_PRE_INITIALIZED"; private static SecurityContextHolderStrategy strategy; //.... private static void initializeStrategy() { if (MODE_PRE_INITIALIZED.equals(strategyName)) { Assert.state(strategy != null, "When using " + MODE_PRE_INITIALIZED + ", setContextHolderStrategy must be called with the fully constructed strategy"); return; } if (!StringUtils.hasText(strategyName)) { // Set default strategyName = MODE_THREADLOCAL; } if (strategyName.equals(MODE_THREADLOCAL)) { strategy = new ThreadLocalSecurityContextHolderStrategy(); return; } if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) { strategy = new InheritableThreadLocalSecurityContextHolderStrategy(); return; } if (strategyName.equals(MODE_GLOBAL)) { strategy = new GlobalSecurityContextHolderStrategy(); return; } //..... } }
MODE THREADLOCAL
:这种存放策略是将 SecurityContext 存放在 ThreadLocal中,大家知道 Threadlocal 的特点是在哪个线程中存储就要在哪个线程中读取,这其实非常适合 web 应用,因为在默认情况下,一个请求无论经过多少 Filter 到达 Servlet,都是由一个线程来处理的。这也是 SecurityContextHolder 的默认存储策略,这种存储策略意味着如果在具体的业务处理代码中,开启了子线程,在子线程中去获取登录用户数据,就会获取不到。MODE INHERITABLETHREADLOCAL
:这种存储模式适用于多线程环境,如果希望在子线程中也能够获取到登录用户数据,那么可以使用这种存储模式。MODE GLOBAL
:这种存储模式实际上是将数据保存在一个静态变量中,在 JavaWeb开发中,这种模式很少使用到。
SecurityContextHolderStrategy
通过 SecurityContextHolder 可以得知,SecurityContextHolderStrategy 接口用来定义存储策略方法
public interface SecurityContextHolderStrategy { void clearContext(); SecurityContext getContext(); void setContext(SecurityContext context); SecurityContext createEmptyContext(); }
接口中一共定义了四个方法:
clearContext
:该方法用来清除存储的 SecurityContext对象。getContext
:该方法用来获取存储的 SecurityContext 对象。setContext
:该方法用来设置存储的 SecurityContext 对象。create Empty Context
:该方法则用来创建一个空的 SecurityContext 对象。
代码中获取认证之后用户数据
@RestController public class HelloController { @RequestMapping("/hello") public String hello() { Authentication authentication = SecurityContextHolder .getContext().getAuthentication(); User principal = (User) authentication.getPrincipal(); System.out.println("身份 :"+principal.getUsername()); System.out.println("凭证 :"+authentication.getCredentials()); System.out.println("权限 :"+authentication.getAuthorities()); return "hello security"; } }
多线程情况下获取用户数据
@RestController public class HelloController { @RequestMapping("/hello") public String hello() { new Thread(()->{ Authentication authentication = SecurityContextHolder .getContext().getAuthentication(); User principal = (User) authentication.getPrincipal(); System.out.println("身份 :"+principal.getUsername()); System.out.println("凭证 :"+authentication.getCredentials()); System.out.println("权限 :"+authentication.getAuthorities()); }).start(); return "hello security"; } }
可以看到默认策略,是无法在子线程中获取用户信息,如果需要在子线程中获取必须使用第二种策略,默认策略是通过 System.getProperty 加载的,因此我们可以通过增加 VM Options 参数进行修改。
-Dspring.security.strategy=MODE_INHERITABLETHREADLOCAL
页面上获取用户信息
引入依赖
<dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> <version>3.0.4.RELEASE</version> </dependency>
页面加入命名空间
<html lang="en" xmlns:th="https://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
页面中使用
<!--获取认证用户名--> <ul> <li sec:authentication="principal.username"></li> <li sec:authentication="principal.authorities"></li> <li sec:authentication="principal.accountNonExpired"></li> <li sec:authentication="principal.accountNonLocked"></li> <li sec:authentication="principal.credentialsNonExpired"></li> </ul>
到此这篇关于Spring Security获取用户认证信息的实现流程的文章就介绍到这了,更多相关Spring Security获取认证信息内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
您可能感兴趣的文章:
- Spring Security整合KeyCloak保护Rest API实现详解
- Spring Security中如何获取AuthenticationManager对象
- SpringSecurity报错authenticationManager must be spec的解决
- Spring Security 登录时添加图形验证码实现实例
- SpringBoot Security权限控制自定义failureHandler实例
- SpringBoot整合Security权限控制登录首页
- SpringBoot 整合Security权限控制的初步配置
- SpringBoot Security使用MySQL实现验证与权限管理