My Spring boot 2.5 application is a fairly standard web application that exposes some endpoints and calls other services. In the usual use cases, an external request comes in, and applies some business logic, which is going to call other services, using WebClient configured with Oauth2.
However, my application also contains a scheduled task that needs to make some calls to other services.
That's where the problems are starting : I believe I face the same issue as in Spring Security 5 Calling OAuth2 Secured API in Application Runner results in IllegalArgumentException , ie servletRequest cannot be null
when using webClient from a non Servlet environment.
I thought I would be able to reproduce this easily through an integration test, but I am not. I already have a SpringBootTest
for the scheduled task, in which I load the Spring context, get access to the class that has the logic, and simply call the method that is scheduled when deployed in prod... but it's not failing : I am not able to reproduce.
When I debug the test, I see the servletRequest is indeed not null when I run the test (while it is when it runs in prod) : there's a Spring MockhttpServletrequest
, so I don't have the same problem as in prod.
I have tried to run the test with no "web" support at all, with
@SpringBootTest(webEnvironment = WebEnvironment.NONE)
But when I do that, some of the required elements are missing for my application to start : I have 2 webClients configured in my application, the "normal" one, and the one I plan to use for the scheduler :
@Bean
@Primary
WebClient servletWebClient(ClientRegistrationRepository clientRegistrations,
OAuth2AuthorizedClientRepository authorizedClients) {
//this constructor will configure internally a RemoveAuthorizedClientOAuth2AuthorizationFailureHandler,
// and onAuthorizationFailure will be called on it when we get a 401
var oauth = new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients);
oauth.setDefaultClientRegistrationId("keycloak");
return WebClient.builder()
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.apply(oauth.oauth2Configuration())
.build();
}
@Bean
@Qualifier("forScheduler")
WebClient schedulerWebClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth =new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth.setDefaultClientRegistrationId("keycloak");
return WebClient.builder()
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.apply(oauth.oauth2Configuration())
.build();
}
@Bean
public OAuth2AuthorizedClientManager authorizedClientManagerForScheduler(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientService clientService)
{
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();
AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
new AuthorizedClientServiceOAuth2AuthorizedClientManager(
clientRegistrationRepository, clientService);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
But when configuring my test with webEnvironment = WebEnvironment.NONE
, then no ClientRegistrationRepository
gets loaded, and the declared servletWebClient
can't be instantiated, as it's one of its dependencies.
Testing this manually is quite difficult/expensive, so I would really like to get some kind of validations through my integration tests that my change with 2 webClients will work before I deploy it.
How can I test that scenario correctly ? or in other words, is there a way to have a default Spring Boot test, but without a mockServletContext being initialized by Spring ?