如何在使用 Async 创建的线程中访问安全上下文

How to reach the security context in a thread created with Async

我将 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 return null。

我尝试在执行程序配置中修复它,我创建了一个 DelegatingSecurityContextExecutor 来包装执行程序和安全上下文。但似乎该 bean 是在 'main' 线程中创建的,并且在执行 RestController 方法时使用的安全上下文并不相同,因此 getAuthentication() 仍然 return 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;
    }

如何正确配置执行程序?

在我看来你不能在这里使用 RequestInterceptor。据我所知,当您使用 @Async 时,您会在要以异步方式执行的方法中丢失请求上下文。为此,您必须显式地将访问令牌传递给异步方法并将其作为请求提供 header:

@FeignClient(name = "userClient", url ="${userService.hostname}")
public interface MyFeignClient {

    String AUTH_TOKEN = “Authorization”;
    @GetMapping(“/users”)
    List<User> findUsers(@RequestHeader(AUTH_TOKEN) String bearerToken);
}

我终于找到了解决方案,可以将安全上下文自动传播到其他线程。

只需将这行代码添加到 spring 启动应用程序的静态 main 方法中即可:

 SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);

解决方案在这里有很好的解释:https://www.baeldung.com/spring-security-async-principal-propagation?fbclid=IwAR1zeGKvRvBb7GG8SmxO4x8-NlKkG39Q29WoLKxZ8NRzyKEcnDWx4Q6EUk0

!!警告 !! :我注意到该解决方案出现了意外行为,至少在我的本地开发环境中是这样。我使用 chrome 的会话框工具(与不同的浏览器相同)使用两个不同的帐户连接到我的应用程序,似乎当我与用户 A SecurityContextHolder.getContext().getAuthentication() 连接时.getPrincipal() return 用户 B 的安全上下文...如此巨大的安全问题!我正在寻找解决方案。

阅读此 post : How to set up Spring Security SecurityContextHolder strategy? the solution seems to be here Spring Security and @Async (Authenticated Users mixed up)