我使用Async的方法调用远程服务的Feign,我需要在请求中附加一个oauth2标记,为此我使用了RequestInterceptor。
@Bean
public RequestInterceptor requestTokenBearerInterceptor() {
return requestTemplate -> {
Object principal = SecurityContextHolder
.getContext()
.getAuthentication()
.getPrincipal();
if (!principal.equals("anonymousUser")) {
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails)
SecurityContextHolder.getContext().getAuthentication().getDetails();
requestTemplate.header("Authorization", "bearer " + details.getTokenValue());
}
};
}
但是当requestInterceptor在另一个线程中使用时,我无法访问相同的安全上下文,所以getAuhentication返回null。
我试图在执行器配置中解决这个问题,我创建了一个DelegatingSecurityContextExecutor,包裹了执行器和安全上下文。但似乎Bean是在 "主 "线程中创建的,而且当RestController方法被执行时,安全上下文与当时使用的不一样,所以getAuthentication()仍然返回null。
@Bean(name = "asyncExecutor")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3);
executor.setMaxPoolSize(3);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("AsynchThread-");
executor.initialize();
Executor wrappedExecutor = new DelegatingSecurityContextExecutor(executor, SecurityContextHolder.getContext());
return wrappedExecutor;
}
我怎样才能正确配置执行器?
我终于找到了解决方案,可以自动将安全上下文传播给其他线程。
只要在你的spring boot应用程序的静态主方法中添加这行代码。
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
这个解决方案在这里有很好的解释。https:/www.baeldung.comspring-security-async-principal-propagation?fbclid=IwAR1zeGKvRvBb7GG8SmxO4x8-NlKkG39Q29WoLKxZ8NRzyKEcnDWx4Q6EUk0
!! WARNING !!!: 我注意到这个解决方案有一个意想不到的行为,至少在我的本地开发环境中是这样。我使用chrome的sessionbox工具用两个不同的账户连接到我的应用程序(不同的浏览器也一样),似乎当我与用户A连接时,SecurityContextHolder.getContext().getAuthentication().getPrincipal()会返回用户B的安全上下文...... 所以巨大的安全问题 我目前正在寻找一个解决方案。
阅读这篇文章。如何设置Spring Security SecurityContextHolder策略? 解决方案似乎在这里 Spring安全和@Async(认证用户混为一谈
在我看来,你不能用 RequestInterceptor
这里。据我所知,当你使用 @Async
你会在你想以异步方式执行的方法中失去请求上下文。要做到这一点,你必须显式地将访问令牌传递给异步方法,并将其作为请求头。
@FeignClient(name = "userClient", url ="${userService.hostname}")
public interface MyFeignClient {
String AUTH_TOKEN = “Authorization”;
@GetMapping(“/users”)
List<User> findUsers(@RequestHeader(AUTH_TOKEN) String bearerToken);
}