• Java21 + SpringBoot3使用Spring Security时如何在子线程中获取到认证信息


    前言

    近日心血来潮想做一个开源项目,目标是做一款可以适配多端、功能完备的模板工程,包含后台管理系统和前台系统,开发者基于此项目进行裁剪和扩展来完成自己的功能开发。本项目为前后端分离开发,后端基于Java21SpringBoot3开发,后端使用Spring SecurityJWTSpring Data JPA等技术栈,前端提供了vueangularreactuniapp微信小程序等多种脚手架工程。

    项目地址:https://gitee.com/breezefaith/fast-alden

    在使用Spring Security时,笔者定义了一个ThreadPoolTaskExecutorBean用于创建子线程,但是却遇到了子线程中无法获取到认证信息的问题,本文主要介绍该问题的解决方案。

    原因分析

    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_THREADLOCALMODE_INHERITABLETHREADLOCALMODE_GLOBAL。如果没有指定,则会默认使用MODE_THREADLOCAL策略。

    • MODE_THREALOCAL表示用户信息只能由当前线程访问。
    • MODE_INHERITABLETHREADLOCAL表示用户信息可以由当前线程及其子线程访问.。
    • MODE_GLOBAL表示用户信息没有线程限制,全局都可以访问,一般用于gui的开发中。

    因此,将安全策略修改为MODE_INHERITABLETHREADLOCAL就可以在子线程中获取到认证信息。

    Spring Security还提供了两种修改安全策略的方式,一种是通过设置JVM参数spring.security.strategy,一种是调用SecurityContextHoldersetStrategyName方法。

    通过设置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时如何在子线程中获取到认证信息。如有错误,还望批评指正。

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

  • 相关阅读:
    linux常用命令(6):mv命令(移动文件/目录)
    LM393 电压比较器及其典型电路介绍
    113. 路径总和ii
    小程序广告变现的收益怎么样?
    父相子绝 巧用auto
    企业架构LB-服务器的负载均衡之LVS实现
    CAP 7.2 版本发布通告
    MySQL数据库的SQL语句
    dnmp一键部署搞定的php开发环境基于Docker的LNMP一键安装程序
    【ECMAScript6】Promise
  • 原文地址:https://www.cnblogs.com/breezefaith/p/18018935