I've got a REST api secured with Spring Security and OAuth2 and JWT. This service is an OAuth client as well, as it needs to connect to other services (using client credentials grant).
Requests to other services, which are as well secured with OAuth is done using OpenFeign and here is the configuration for 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;
}
}
}
All this works as expected in a sync configuration: Feign configuration requests and injeect a JWT in the header. Problems started when tried to rewrite the calls using async (either Runnable or @Async have the same result).
As explained here and here the Security Context is not propagated by default to other threads, for which I need to manually configure
@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);
}
Now I can see that the context gets propagated to the async thread. However, when trying to authenticate the OpenFeign client, an exception is thrown in
var client = manager.authorize(oAuth2AuthorizeRequest)
as internally it calls
HttpServletRequest servletRequest = getHttpServletRequestOrDefault(authorizeRequest.getAttributes());
but the servletRequest
is null and the credentials are not set in the request.
Any idea on how to pass the HttpContext to a thread? Should I be concerned by 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 ?