前言
近日心血来潮想做一个开源项目,目标是做一款可以适配多端、功能完备的模板工程,包含后台管理系统和前台系统,开发者基于此项目进行裁剪和扩展来完成自己的功能开发。本项目为前后端分离开发,后端基于Java21
和SpringBoot3
开发,后端使用Spring Security
、JWT
、Spring Data JPA
等技术栈,前端提供了vue
、angular
、react
、uniapp
、微信小程序
等多种脚手架工程。
项目地址:https://gitee.com/breezefaith/fast-alden
在使用Spring Security
时,笔者定义了一个ThreadPoolTaskExecutor
Bean用于创建子线程,但是却遇到了子线程中无法获取到认证信息的问题,本文主要介绍该问题的解决方案。
原因分析
在 Spring Security
中想要获取登录用户信息,只能在当前线程中获取,不能在子线程中获取,其中一个重要的原因就是SecurityContextHolder
默认将用户信息保存在ThreadLocal
中。
ThreadLocal
叫做本地线程变量
,意思是说,ThreadLocal
中填充的的是当前线程的变量,该变量对其他线程而言是封闭且隔离的,ThreadLocal
为变量在每个线程中创建了一个副本,这样每个线程都可以访问自己内部的副本变量。
解决方案
方案1:手动设置线程中的认证信息
在子线程的业务逻辑代码之前先手动设置认证信息,后续就可以通过SecurityContextHolder
直接获取。
// 获取当前线程认证信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// 创建新线程
Runnable runnable = new Runnable() {
public void run() {
// 手动设置线程中的认证信息
SecurityContextHolder.getContext().setAuthentication(authentication);
// 线程处理逻辑(后续就能获取到认证信息)
// ...
}
};
new Thread(runnable).start();
方案2:使用DelegatingSecurityContextRunnable
创建线程
Spring Security
考虑到了新线程需要访问认证信息的情况,提供了DelegatingSecurityContextRunnable
类,通过该类构建新线程(返回一个Runnable
对象),线程内部自然能获取认证信息。有兴趣的读者可以阅读一下DelegatingSecurityContextRunnable
的源码,其思路与方法1是一致的,都是先获取到当前线程的认证信息,然后传递给新线程。
// 使用DelegatingSecurityContextRunnable创建线程
Runnable runnable = new DelegatingSecurityContextRunnable(() -> {
// 线程处理逻辑
// ...
});
new Thread(runnable).start();
方案3:修改Spring Security
安全策略
默认情况下,Spring Security
使用ThreadLocal
存储认证信息,但实际上它也支持通过设置安全策略来修改认证信息的存储位置,它支持三种安全策略,有MODE_THREADLOCAL
、MODE_INHERITABLETHREADLOCAL
和MODE_GLOBAL
。如果没有指定,则会默认使用MODE_THREADLOCAL
策略。
MODE_THREALOCAL
表示用户信息只能由当前线程访问。MODE_INHERITABLETHREADLOCAL
表示用户信息可以由当前线程及其子线程访问.。MODE_GLOBAL
表示用户信息没有线程限制,全局都可以访问,一般用于gui的开发中。
因此,将安全策略修改为MODE_INHERITABLETHREADLOCAL
就可以在子线程中获取到认证信息。
Spring Security
还提供了两种修改安全策略的方式,一种是通过设置JVM参数spring.security.strategy
,一种是调用SecurityContextHolder
的setStrategyName
方法。
通过设置JVM参数修改安全策略
-Dspring.security.strategy=MODE_INHERITABLETHREADLOCAL
通过SecurityContextHolder
修改安全策略
可以借助@PostConstruct
注解在程序启动后修改安全策略。
@Configuration
public class CommonSecurityConfig {
@PostConstruct
public void setStrategyName() {
// 程序启动后修改认证信息上下文存储策略,支持子线程中获取认证信息
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
}
}
@PostConstruct
注解是由Java提供的,它用来修饰一个非静态的void方法。它会在服务器加载Servlet的时候运行,并且只运行一次。
总结
本文主要介绍SpringBoot3
使用Spring Security
时如何在子线程中获取到认证信息。如有错误,还望批评指正。
在后续实践中我也是及时更新自己的学习心得和经验总结,希望与诸位看官一起进步。