添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement . We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) status: invalid An issue that we don't feel is valid

In the reference doc there is an example for a WebClient with OAuth2 Setup for Reactive Applications: https://docs.spring.io/spring-security/site/docs/current/reference/html5/#webclient-setup

    @Bean
    WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations, ServerOAuth2AuthorizedClientRepository authorizedClients) {
        ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
                new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients);
        oauth.setDefaultClientRegistrationId("keycloak");
        return WebClient.builder()
                .filter(oauth)
                .build();

But in my szenario it leads to an exception:

java.lang.IllegalArgumentException: serverWebExchange cannot be null
	at org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager.lambda$authorize$4(DefaultReactiveOAuth2AuthorizedClientManager.java:131) ~[spring-security-oauth2-client-5.3.1.RELEASE.jar:5.3.1.RELEASE]
	Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
	|_ checkpoint ⇢ Request to GET https://abc.de/service/api/endpoint?x=0&y=0&z=0 [DefaultWebClient]
Stack trace:
		at org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager.lambda$authorize$4(DefaultReactiveOAuth2AuthorizedClientManager.java:131) ~[spring-security-oauth2-client-5.3.1.RELEASE.jar:5.3.1.RELEASE]
		at reactor.core.publisher.MonoErrorSupplied.subscribe(MonoErrorSupplied.java:70) ~[reactor-core-3.3.4.RELEASE.jar:3.3.4.RELEASE]
		at reactor.core.publisher.Mono.subscribe(Mono.java:4210) ~[reactor-core-

However, switching the ServerOAuth2AuthorizedClientRepository to a ReactiveOAuth2AuthorizedClientService makes the code run.

    @Bean
    WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations, ReactiveOAuth2AuthorizedClientService authorizedClientService) {
        ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(clientRegistrations, authorizedClientService));
        oauth.setDefaultClientRegistrationId("keycloak");
        return WebClient.builder()
                .filter(oauth)
                .build();
spring-security-config:5.3.1.RELEASE
spring-security-oauth2-client:5.3.1.RELEASE
spring-boot-starter-parent:2.2.6.RELEASE
spring-boot-starter-webflux:2.2.6.RELEASE

Is that an issue or am I handling something wrong?
I am not sure if there is a correlation but, the working code example does not retrieve a new token, when Mono.retryWhen(...) is used.

I experience the same thing. When doing av webClient.get() outside Servlet context i get "servletRequest cannot be null".

If I do the whole call inside a @controller or @RestController it works fine.

@fabian-froehlich @Avec112 The issue here is that the OAuth 2.0 Client Reactive documentation is out-of-date and missing quite a bit of content compared to the Servlet sections.

Take a look at the OAuth2AuthorizedClientManager / OAuth2AuthorizedClientProvider (Servlet) docs:

The DefaultOAuth2AuthorizedClientManager is designed to be used within the context of a HttpServletRequest. When operating outside of a HttpServletRequest context, use AuthorizedClientServiceOAuth2AuthorizedClientManager instead.

Since 5.2, it's recommended to use the OAuth2AuthorizedClientManager constructor.

However, switching the ServerOAuth2AuthorizedClientRepository to a ReactiveOAuth2AuthorizedClientService makes the code run.

This makes sense, however, I would recommend using the ReactiveOAuth2AuthorizedClientManager constructor and pass in AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager.

We have #8174 logged to get the Reactive docs in sync with the Servlet docs.

I'll close this issue as answered. If something is still not clear let me know and we'll address it.

Hi @jgrandja,
thanks for your reply.

If I am understanding you correct, then your recommendet way is what I wrote in my initial post as a running example, right? And it seems that I am outside of a HttpServletRequest.
If I need to change the code in order to work as expected, could you give an example?

Could you give me an insight, if any possible error here, results in my finding, that Mon.retryWhen(..) does not handle a correct token refresh, when retrys are triggered?

Kind regards,
Fabian Fröhlich

@fabian-froehlich

There are plenty of examples in the reference documentation so please take a look there. Again, the reactive docs are out of date so check out the Servlet docs (the only difference between Servlet and Reactive are the class names).

  • OAuth2AuthorizedClientManager / OAuth2AuthorizedClientProvider see AuthorizedClientServiceOAuth2AuthorizedClientManager (for reactive see AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager)
  • WebClient integration for Servlet Environments
  • Hi @jgrandja and sorry for your trouble.
    If the only difference is the class name then there might be an issue because the following config still results in an java.lang.IllegalArgumentException: serverWebExchange cannot be null when following OAuth2AuthorizedClientManager / OAuth2AuthorizedClientProvider and exchanging the classes.

        @Bean
        public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
                ReactiveClientRegistrationRepository clientRegistrationRepository,
                ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
            ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
                    ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
                            .authorizationCode()
                            .refreshToken()
                            .clientCredentials()
                            .password()
                            .build();
            DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
                    new DefaultReactiveOAuth2AuthorizedClientManager(
                            clientRegistrationRepository, authorizedClientRepository);
            authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
            return authorizedClientManager;
    
        @Bean
        WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
            ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
            oauth.setDefaultClientRegistrationId("keycloak");
            return WebClient.builder()
                    .filter(oauth)
                    .build();
              

    @fabian-froehlich

    DefaultReactiveOAuth2AuthorizedClientManager is intended to be used within a request context.

    Given that you're seeing serverWebExchange cannot be null, you must be operating outside of a request context, which in case you should use AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager instead.

    NOTE: Change the ServerOAuth2AuthorizedClientRepository parameter to ReactiveOAuth2AuthorizedClientService.

    Added WebClientConfiguration class like described above but when I'm trying to run app I see in log:
    Consider defining a bean of type 'org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository' in your configuration.

    How could it be solved?

    Here code of my class:

    import io.netty.channel.ChannelOption;
    import io.netty.handler.timeout.ReadTimeoutHandler;
    import io.netty.handler.timeout.WriteTimeoutHandler;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    import org.springframework.http.client.reactive.ReactorClientHttpConnector;
    import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
    import org.springframework.security.oauth2.client.AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager;
    import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProvider;
    import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProviderBuilder;
    import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
    import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
    import org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction;
    import org.springframework.web.reactive.function.client.WebClient;
    import reactor.netty.http.client.HttpClient;
    @Configuration
    @EnableWebFlux
    public class WebClientConfiguration {
      @Primary
      @Bean
      public WebClient webClient(ServiceProperties properties,
          AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager auth2AuthorizedClientManager) {
        ServerOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
            new ServerOAuth2AuthorizedClientExchangeFilterFunction(auth2AuthorizedClientManager);
        oauth2Client.setDefaultClientRegistrationId("keycloak");
        HttpClient httpClient = HttpClient.create()
            .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10 * 1000)
            .doOnConnected(connection -> {
              connection.addHandlerLast(new ReadTimeoutHandler(2 * 60 * 1000L, MILLISECONDS));
              connection.addHandlerLast(new WriteTimeoutHandler(2 * 60 * 1000L, MILLISECONDS));
        return WebClient.builder()
            .baseUrl(properties.getUrl())
            .filter(oauth2Client)
            .clientConnector(new ReactorClientHttpConnector(httpClient))
            .build();
      @Bean
      public AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager(
          ReactiveClientRegistrationRepository clientRegistrationRepository,
          ReactiveOAuth2AuthorizedClientService reactiveOAuth2AuthorizedClientService) {
        ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
            ReactiveOAuth2AuthorizedClientProviderBuilder.builder().clientCredentials().build();
        AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager =
            new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
                clientRegistrationRepository, reactiveOAuth2AuthorizedClientService);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
        return authorizedClientManager;
    

    SOLVED.
    Need to add ReactiveClientRegistrationRepository bean:

      @Bean
      ReactiveClientRegistrationRepository clientRegistrations(
          @Value("${spring.security.oauth2.client.provider.keycloak.token-uri}") String token_uri,
          @Value("${spring.security.oauth2.client.registration.keycloak.client-id}") String client_id,
          @Value("${spring.security.oauth2.client.registration.keycloak.client-secret}") String client_secret,
          @Value("${spring.security.oauth2.client.registration.keycloak.authorization-grant-type}") String authorizationGrantType
        ClientRegistration registration = ClientRegistration
            .withRegistrationId("keycloak")
            .tokenUri(token_uri)
            .clientId(client_id)
            .clientSecret(client_secret)
            .authorizationGrantType(new AuthorizationGrantType(authorizationGrantType))
            .build();
        return new InMemoryReactiveClientRegistrationRepository(registration);
    

    and change AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager bean:

      @Bean
      public AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager(
          ReactiveClientRegistrationRepository clientRegistrationRepository) {
        InMemoryReactiveOAuth2AuthorizedClientService clientService =
            new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrationRepository);
        ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
            ReactiveOAuth2AuthorizedClientProviderBuilder.builder().clientCredentials().build();
        AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager =
            new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
                clientRegistrationRepository, clientService);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
        return authorizedClientManager;
              

    @andrecampanini, just a note that since the last comment on this thread, we have updated the reactive section of the documentation. See the section on ReactiveOAuth2AuthorizedClientManager. It may be worth reading through the entire chapter in context. If you have anything that looks like a bug, feel free to file a new issue with a minimal sample that reproduces the issue.

    in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) status: invalid An issue that we don't feel is valid