Spring Security 框架提供了 3 种策略来管理 SecurityContext. 管理该类的对象是 SecurityContextHolder。
- MODE_THREALOCAL: 允许每个线程在安全上下文存储自己的详细信息,在每个请求一个线程的 web应用程序中,这是一种常见的方法,因为每个请求都是一个单独的线程。这个策略也是 Spring Security 默认使用的策略。
- MODE_INHERITABLETHREADLOCAL: 类似于 MODE_THREALOCAL,但是从名称中可以知道,这是一个可以继承的模式,所以这种模式,可以在使用异步方法时,将安全上下文复制到下一个线程,在运行带有@Async 注解的异步方法时,调用该方法的线程也能继承到该安全上下文。
- 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;
}
}