前言

近日心血来潮想做一个开源项目,目标是做一款可以适配多端、功能完备的模板工程,包含后台管理系统和前台系统,开发者基于此项目进行裁剪和扩展来完成自己的功能开发。本项目为前后端分离开发,后端基于
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

image

通过
SecurityContextHolder
修改安全策略

可以借助
@PostConstruct
注解在程序启动后修改安全策略。

@Configuration
public class CommonSecurityConfig {
    @PostConstruct
    public void setStrategyName() {
        // 程序启动后修改认证信息上下文存储策略,支持子线程中获取认证信息
        SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
    }
}

@PostConstruct
注解是由Java提供的,它用来修饰一个非静态的void方法。它会在服务器加载Servlet的时候运行,并且只运行一次。
image

总结

本文主要介绍
SpringBoot3
使用
Spring Security
时如何在子线程中获取到认证信息。如有错误,还望批评指正。

在后续实践中我也是及时更新自己的学习心得和经验总结,希望与诸位看官一起进步。

标签: none

添加新评论