解决异步任务无法获取到Token中的用户信息问题

文章目录

问题描述

执行异步任务后,下方代码在打断点后发现获取到的auth为null。

java 复制代码
Authentication auth = SecurityContextHolder.getContext().getAuthentication();

原因

这是Spring Security 的 SecurityContext 是基于 ThreadLocal 实现的,而异步任务会在新的线程中执行,ThreadLocal 数据不会自动传递到新线程。

解决方法

方法一:手动传递 SecurityContext

  • 在主线程中获取context

    java 复制代码
    SecurityContext context = SecurityContextHolder.getContext();
  • 在新线程中设置context

    java 复制代码
    SecurityContextHolder.setContext(context);

    后续可以拿到用户信息

    java 复制代码
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    // 执行业务逻辑...
  • 在最后要清除context

    java 复制代码
    SecurityContextHolder.clearContext();

    如果不执行改操作,SecurityContext 会一直存在于线程中,上一个任务的用户权限会影响到下一个任务。

方法二:传递用户信息

如果需要用户的具体信息,比如user_id,可以直接传递user_id

java 复制代码
@Async
public CompletableFuture<Void> asyncTask(Long userId) {
    // 通过参数传递用户ID,而不是从 SecurityContext 获取
    AuthUsers user = userRepository.findById(userId);
    // 执行业务逻辑...
    return CompletableFuture.completedFuture(null);
}

// 调用时
@Autowired
private YourService yourService;

public void someMethod() {
    Long currentUserId = SecurityUtil.getCurrentUserId();
    yourService.asyncTask(currentUserId);
}

方法三:自定义 TaskDecorator

能让 Spring Security 的 SecurityContext(包含用户认证信息)在异步任务中自动传递,而不需要手动传递和清理。

原理:使用 装饰器模式 在异步任务执行前后自动设置和清理 SecurityContext。

java 复制代码
@Configuration
@EnableAsync  // 启用 Spring 异步支持
public class AsyncConfig implements AsyncConfigurer {
    
    @Override
    public Executor getAsyncExecutor() {
        // 创建线程池
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(50);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("async-");
        executor.initialize();
        
        // ✅ 关键:设置任务装饰器
        executor.setTaskDecorator(new SecurityContextCopyingDecorator());
        return executor;
    }
    
    // 装饰器类
    static class SecurityContextCopyingDecorator implements TaskDecorator {
        @Override
        public Runnable decorate(Runnable runnable) {
            // 1. 在主线程中获取当前的 SecurityContext
            SecurityContext context = SecurityContextHolder.getContext();
            
            // 2. 返回一个新的 Runnable,它会:
            return () -> {
                try {
                    // a) 在新线程中设置 SecurityContext
                    SecurityContextHolder.setContext(context);
                    // b) 执行原始任务
                    runnable.run();
                } finally {
                    // c) 清理 SecurityContext(避免内存泄漏)
                    SecurityContextHolder.clearContext();
                }
            };
        }
    }
}

总结

  1. 异步任务是在新的线程执行,而SecurityContext 是基于 ThreadLocal 实现的,所以在异步任务中不能直接通过SecurityContext 获取用户信息。
  2. 核心是对上下文的传递,可以是手动传递也可以是装饰器设置。
  3. 用完上下文后,记得清除,避免影响下一个任务。

以上为个人学习分享,如有问题,欢迎指出:)

相关推荐
朝新_15 小时前
【Spring AI 】核心知识体系梳理:从入门到实战
java·人工智能·spring
谁怕平生太急16 小时前
面试题记录:在线数据迁移
java·数据库·spring
云烟成雨TD18 小时前
Spring AI Alibaba 1.x 系列【18】Hook 接口和四大抽象类
java·人工智能·spring
Flittly20 小时前
【SpringSecurity新手村系列】(2)整合 MyBatis 实现数据库认证
java·安全·spring·springboot·安全架构
devilnumber21 小时前
java中Redisson ,jedis,Lettuce和Spring Data Redis的四种深度对比和优缺点详解
java·redis·spring
砍材农夫1 天前
spring-ai 第十一mcp server调用入门(stdio协议)
人工智能·spring·microsoft
码农阿豪1 天前
一次 AI 调用 15 万 Token 只花了 $0.058?彻底搞懂 Token、缓存读、补全计费机制!(附完整架构图)
人工智能·spring·缓存
awljwlj1 天前
黑马点评复习—缓存相关【包含可能的问题和基础知识复习】
java·后端·spring·缓存
极光代码工作室1 天前
基于SpringBoot的在线考试系统
java·springboot·web开发·后端开发
庞轩px1 天前
反射与动态代理——Java语言动态性的核心
java·spring·反射·aop·动态代理·类型