我有一个使用 Spring Security、OAuth2 和 JWT 保护的 REST api。该服务也是 OAuth 客户端,因为它需要连接到其他服务(使用客户端凭据授予)。
使用 OpenFeign 完成对其他服务的请求,这些服务也受到 OAuth 的保护,这里是 OAuth2 的配置。
@Slf4j
@Configuration
public class OAuth2OpenFeignConfig {
@Value("${client-name}")
private String clientName;
private final ClientRegistrationRepository clientRegistrationRepository;
public OAuth2OpenFeignConfig(ClientRegistrationRepository clientRegistrationRepository) {
this.clientRegistrationRepository = clientRegistrationRepository;
}
@Bean
public RequestInterceptor requestInterceptor(OAuth2AuthorizedClientManager authorizedClientManager) {
var clientRegistration = clientRegistrationRepository.findByRegistrationId(clientName);
var clientCredentialsFeignManager = new OAuthClientCredentialsFeignManager();
return requestTemplate -> requestTemplate.header("Authorization", "Bearer " + clientCredentialsFeignManager.getAccessToken(authorizedClientManager, clientRegistration));
}
static class OAuthClientCredentialsFeignManager {
public String getAccessToken(OAuth2AuthorizedClientManager manager, ClientRegistration clientRegistration) {
try {
var oAuth2AuthorizeRequest = OAuth2AuthorizeRequest
.withClientRegistrationId(clientRegistration.getRegistrationId())
.principal(SecurityContextHolder.getContext().getAuthentication())
.build();
var client = manager.authorize(oAuth2AuthorizeRequest);
if (isNull(client)) {
throw new IllegalStateException("client credentials flow on " + clientRegistration.getRegistrationId() + " failed, client is null");
}
return client.getAccessToken().getTokenValue();
} catch (Exception ex) {
log.error("client credentials error " + ex.getMessage());
}
return null;
}
}
}
所有这些都在同步配置中按预期工作:假装配置请求并在标头中注入 JWT。当尝试使用异步重写调用时出现问题(Runnable 或 @Async 具有相同的结果)。
如here和here所解释,安全上下文默认不会传播到其他线程,我需要手动配置
@Bean("threadPoolTaskExecutor")
public TaskExecutor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20);
executor.setMaxPoolSize(1000);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setThreadNamePrefix("Async-");
executor.initialize(); // this is important, otherwise an error is thrown
return new DelegatingSecurityContextAsyncTaskExecutor(executor);
}
现在我可以看到上下文被传播到异步线程。然而,当尝试验证 OpenFeign 客户端时,会抛出异常
var client = manager.authorize(oAuth2AuthorizeRequest)
正如其内部调用的那样
HttpServletRequest servletRequest = getHttpServletRequestOrDefault(authorizeRequest.getAttributes());
但是
servletRequest
为空,并且请求中未设置凭据。
关于如何将 HttpContext 传递给线程有什么想法吗?我应该担心 https://docs.spring.io/spring-framework/docs/5.0.2.RELEASE/kdoc-api/spring-framework/org.springframework.web.filter/-request-context-filter /set-thread-context-inheritable.html ?
请查看AuthorizedClientServiceOAuth2AuthorizedClientManager。它能够在 servlet 上下文之外工作。
以下代码对我来说非常有用
@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientService authorizedClientRepository) {
var authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();
var authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(
clientRegistrationRepository,
authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}