'`servletRequest cannot be null` -> SpringBoot - WebClient - GET request with Keycloak

I'm trying to make GET request between two microservices (with Keycloak authentication).

Let's say microservice A is asking microservice B for some resources. Microservice B has a GET endpoint and it's seems to work because I can see correct response when doing request from postman or intelliJ http_client.

In microservice A I'm trying to do a request (I did try to make blocking and non-blocking request):

  • blocking request
String response = webClient.mutate()
                .baseUrl(this.serverUri)
                .build().get()
                .uri(uriBuilder -> uriBuilder
                        .path("/users/tokens/{id}")
                        .build(userId))
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .attributes(ServerOAuth2AuthorizedClientExchangeFilterFunction
                        .clientRegistrationId("keycloak"))
                .retrieve()
                .bodyToMono(String.class)
                .doOnError(RuntimeException::new)
                .block();
  • non-blocking request:
        webClient.mutate()
                .baseUrl(this.serverUri)
                .build().get()
                .uri(uriBuilder -> uriBuilder
                        .path("/users/tokens/{id}")
                        .build(userId))
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .attributes(ServerOAuth2AuthorizedClientExchangeFilterFunction
                        .clientRegistrationId("keycloak"))
                .retrieve()
                .bodyToMono(String.class)
                .subscribe(resp -> {
                    JSONObject jsonObject = new JSONObject(resp);
                    JSONArray jsonArray = jsonObject.getJSONArray("Tokens");
                    for (int i = 0; i < jsonArray.length(); i++) {
                        log.info("token :: " + jsonArray.get(i).toString());
                    }
                });

This is my WebClient configuration:

@Configuration
public class WebClientConfiguration {

    @Bean
    public OAuth2AuthorizedClientManager authorizedClientManager(
            ClientRegistrationRepository clientRegistrationRepository,
            OAuth2AuthorizedClientRepository authorizedClientRepository) {

        OAuth2AuthorizedClientProvider authorizedClientProvider =
                OAuth2AuthorizedClientProviderBuilder.builder()
                        .refreshToken()
                        .clientCredentials()
                        .build();

        DefaultOAuth2AuthorizedClientManager authorizedClientManager =
                new DefaultOAuth2AuthorizedClientManager(
                        clientRegistrationRepository, authorizedClientRepository);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

        return authorizedClientManager;
    }

    @Bean
    WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
                new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
        return WebClient.builder()
                .apply(oauth2Client.oauth2Configuration())
                .build();
    }
}

Everything I did try ends with this kind of error:

2021-06-29 16:44:07.854 ERROR 390692 --- [oundedElastic-1] reactor.core.publisher.Operators         : Operator called default onErrorDropped

reactor.core.Exceptions$ErrorCallbackNotImplemented: java.lang.IllegalArgumentException: servletRequest cannot be null
Caused by: java.lang.IllegalArgumentException: servletRequest cannot be null
    at org.springframework.util.Assert.notNull(Assert.java:201) ~[spring-core-5.3.5.jar:5.3.5]
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
    |_ checkpoint ⇢ Request to GET https://localhost:8080/api/users/tokens/94a2d4f7-b372-4e13-aa16-7b244c099721 [DefaultWebClient]
Stack trace:
        at org.springframework.util.Assert.notNull(Assert.java:201) ~[spring-core-5.3.5.jar:5.3.5]
        at org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager.authorize(DefaultOAuth2AuthorizedClientManager.java:144) ~[spring-security-oauth2-client-5.4.5.jar:5.4.5]
        at org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.lambda$authorizeClient$24(ServletOAuth2AuthorizedClientExchangeFilterFunction.java:552) ~[spring-security-oauth2-client-5.4.5.jar:5.4.5]
        at reactor.core.publisher.MonoSupplier.call(MonoSupplier.java:85) ~[reactor-core-3.4.4.jar:3.4.4]
        at reactor.core.publisher.FluxSubscribeOnCallable$CallableSubscribeOnSubscription.run(FluxSubscribeOnCallable.java:227) ~[reactor-core-3.4.4.jar:3.4.4]
        at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:68) [reactor-core-3.4.4.jar:3.4.4]
        at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:28) [reactor-core-3.4.4.jar:3.4.4]

Am I'm missing something?

[EDIT]

Changing WebClientConfiguration as suggested here (Spring Security 5 Calling OAuth2 Secured API in Application Runner results in IllegalArgumentException) did the trick:

    @Bean
    public OAuth2AuthorizedClientManager authorizedClientManager(
            ClientRegistrationRepository clientRegistrationRepository,
            OAuth2AuthorizedClientService clientService) {

        OAuth2AuthorizedClientProvider authorizedClientProvider =
                OAuth2AuthorizedClientProviderBuilder.builder()
                        .refreshToken()
                        .clientCredentials()
                        .build();

        AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
                new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, clientService);

        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

        return authorizedClientManager;
    }

    @Bean
    WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
                new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
        return WebClient.builder()
                .apply(oauth2Client.oauth2Configuration())
                .build();
    }

Can someone explain why?



Solution 1:[1]

I'm not sure what context you are running this in. But the error message suggests that the request was not started in the context of an HttpServletRequest. If your request was started by a scheduler or something similar, then there is no servletRequest.

The Spring doc says that you need the AuthorizedClientServiceOAuth2AuthorizedClientManager implementation for exactly this case.

DefaultOAuth2AuthorizedClientManager [...] The default implementationof an OAuth2AuthorizedClientManager for use within the context of a HttpServletRequest. (When operating outside of the context of a HttpServletRequest, use AuthorizedClientServiceOAuth2AuthorizedClientManager instead.)

from the official docs

AuthorizedClientServiceOAuth2AuthorizedClientManager [...] An implementation of an OAuth2AuthorizedClientManager that is capable of operating outside of the context of a HttpServletRequest, e.g. in a scheduled/background thread and/or in the service-tier. (When operating within the context of a HttpServletRequest, use DefaultOAuth2AuthorizedClientManager instead.)

from the official docs

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 random