在多线程异步方法中 Spring Security 框架的 SecurityContext 无法获取认证信息的原因及解决方案

Spring Security 框架提供了 3 种策略来管理 SecurityContext. 管理该类的对象是 SecurityContextHolder。

  1. MODE_THREALOCAL: 允许每个线程在安全上下文存储自己的详细信息,在每个请求一个线程的 web应用程序中,这是一种常见的方法,因为每个请求都是一个单独的线程。这个策略也是 Spring Security 默认使用的策略。
  2. MODE_INHERITABLETHREADLOCAL: 类似于 MODE_THREALOCAL,但是从名称中可以知道,这是一个可以继承的模式,所以这种模式,可以在使用异步方法时,将安全上下文复制到下一个线程,在运行带有@Async 注解的异步方法时,调用该方法的线程也能继承到该安全上下文。
  3. MODE_GLOBAL: 使引用程序的所欲线程看到相同的安全上下文实例。

由于 Spring Security 默认使用的是 MODE_THREALOCAL 模式,该模式只允许在请求的主线程中获取安全上下文信息,用来获取用户身份信息。但是如果请求的接口中,又调用了异步方法,或者自定义了线程池去执行方法,则会获取不到用户信息。

 Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

authentication 获取的是个null值
所以在使用authentication 的时候要判断是否为空,防止空指针异常

 public String getUsername() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null) {
            return "anonymousUser";
        }
        return authentication.getName();
    }

解决方案:
@Async 注解的异步方法。那么就需要自定义配置,修改程序默认的策略。
1.定义配置也很简单,只需要在配置类中注入一个 bean 即可,覆盖 Spring Security 默认的安全上下文策略即可

@Bean
    public InitializingBean initializingBean() {
        return () -> SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
    }

2.在异步类中构造方法中自定义

public xxxx() {
        SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
    }

使用如下

@Configuration
@EnableAsync
public class AsyncConfig {
    @Bean
    public InitializingBean initializingBean() {
        return () -> SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
    }
 //   public AsyncConfig() {
 //       SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
 //   }
    @Bean("discoveryExecutor")
    public TaskExecutor discoveryExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 设置核心线程数
        executor.setCorePoolSize(5);
        // 设置最大线程数
        executor.setMaxPoolSize(10);
        // 设置队列容量
        executor.setQueueCapacity(50);
        // 设置线程活跃时间(秒)
        executor.setKeepAliveSeconds(60);
        // 设置默认线程名称
        executor.setThreadNamePrefix("discovery-");
        // 设置拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

        // 等待所有任务结束后再关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        return executor;
    }
  }
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Security,你可以使用`SecurityContextHolder`类的`getContext()`方法获取当前的`SecurityContext`对象。通过`SecurityContext`对象,你可以进一步获取当前用户的认证信息。 以下是获取登录信息的示例代码: ```java Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null && authentication.isAuthenticated()) { Object principal = authentication.getPrincipal(); if (principal instanceof UserDetails) { UserDetails userDetails = (UserDetails) principal; // 获取用户的用户名 String username = userDetails.getUsername(); // 获取用户的权限(角色) Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities(); // 其他操作... } } ``` 在上面的代码,首先通过`SecurityContextHolder.getContext().getAuthentication()`获取当前的`Authentication`对象。然后,你可以检查`authentication`是否为非空并且已经通过身份验证。 如果通过身份验证,你可以通过`authentication.getPrincipal()`获取到用户的主体对象。在大多数情况下,主体对象将是一个实现了`UserDetails`接口的类,它包含了用户的详细信息,如用户名、密码、权限等。 你可以根据需要对`principal`进行类型转换,并使用相应的方法获取用户的用户名、权限等信息。 需要注意的是,在未经身份验证时,`authentication`可能为`null`或包含一个特殊的未经身份验证的实现类,具体取决于你的配置和登录状态。因此,在使用时需要进行适当的判断和处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值