Search code examples
spring-mvcspring-securityspring-webfluxspring-security-oauth2spring-oauth2

Initialising OAuth WebClient Bean in Spring MVC


I have a WebApp JSP project deployed on Weblogic 12 as a WAR.

My gradle build includes mvc and webflux:

implementation 'org.springframework.boot:spring-boot-starter-web:2.3.2.RELEASE'
implementation ("org.springframework.boot:spring-boot-starter-security:2.3.2.RELEASE")
implementation ("org.springframework.boot:spring-boot-starter-oauth2-client:2.3.2.RELEASE")
implementation ("org.springframework.boot:spring-boot-starter-webflux:2.3.2.RELEASE")

I am trying to configure OAuth2 to use client_credentials flow from my client JSP application.

I need the @Controller class to use WebClient and propagate the access token to a Resource Server.

My Bean to create the WebClient is seen below.

@Bean
public ReactiveClientRegistrationRepository getRegistration() {
    ClientRegistration registration = ClientRegistration
            .withRegistrationId("ei-gateway")
            .tokenUri("https://xxxxx.xxxxxxx.net/auth/oauth/v2/token")
            .clientId("xxx-xxxx-43e9-a407-xxxxx")
            .clientSecret("xxxxxx-3d21-4905-b6e5-xxxxxxxxxx")
            .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
            .build();
    return new InMemoryReactiveClientRegistrationRepository(registration);
}


@Bean
public WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations, ServerOAuth2AuthorizedClientRepository authorizedClients) {

    ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients);
    oauth.setDefaultOAuth2AuthorizedClient(true);
    return WebClient.builder()
            .filter(oauth)
            .defaultHeader("accept", "application/json")
            .defaultHeader("content-type", "application/json")
            .defaultHeader("environment", environment)
            .filter(logRequest())
            .filter(logResponse())
            .build();
}

However I get the following error during compile:

Could not autowire. There is more than one bean of 'ReactiveClientRegistrationRepository' type.
Beans:
clientRegistrationRepository   (ReactiveOAuth2ClientConfigurations.class) 
getRegistration   (WebSecurityConfiguration.java) 

However when I uncomment out the getRegistration Bean method and configure the oauth client registration via the web.xml, then when deploying the application I get this error:

org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}:org.springframework.beans.factory.NoSuchBeanDefinitionException:No qualifying bean of type 'org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

I see from the ReactiveOAuth2ClientAutoConfiguration source code that the Reactive OAuth2 Auto Configuration is not run when ReactiveOAuth2ClientAutoConfiguration.NonServletApplicationCondition is set.

@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(ReactiveSecurityAutoConfiguration.class)
@EnableConfigurationProperties(OAuth2ClientProperties.class)
@Conditional(ReactiveOAuth2ClientAutoConfiguration.NonServletApplicationCondition.class)
@ConditionalOnClass({ Flux.class, EnableWebFluxSecurity.class, ClientRegistration.class })
@Import({ ReactiveOAuth2ClientConfigurations.ReactiveClientRegistrationRepositoryConfiguration.class,
        ReactiveOAuth2ClientConfigurations.ReactiveOAuth2ClientConfiguration.class })
public class ReactiveOAuth2ClientAutoConfiguration {
}

Can anyone suggest a course of action? Is is possible to manually configure the ReactiveOAuth2ClientConfiguration?

Thanks


Solution

  • Form what I understand ReactiveClientRegistrationRepository is not available since you are not using a reactive stack, and here's how you can set up WebClient to be used in a Servlet environment.

    Setup application properties so Spring autowires ClientRegistrationRepository and OAuth2AuthorizedClientRepository for you.

    spring.security.oauth2.client.provider.my-oauth-provider.token-uri=https://xxxxx.xxxxxxx.net/auth/oauth/v2/token
    spring.security.oauth2.client.registration.ei-gateway.client-id=xxx-xxxx-43e9-a407-xxxxx
    spring.security.oauth2.client.registration.ei-gateway.client-xxxxxx-3d21-4905-b6e5-xxxxxxxxxx
    spring.security.oauth2.client.registration.ei-gateway.provider=my-oauth-provider
    spring.security.oauth2.client.registration.ei-gateway.scope=read,write
    spring.security.oauth2.client.registration.ei-gateway.authorization-grant-type=client_credentials
    

    Setup configuration to indicate that your application needs to act as an oauth2 Client

    @EnableWebSecurity
    public class WebSecurity extends WebSecurityConfigurerAdapter {
    
         @Override
         protected void configure(HttpSecurity http) throws Exception {
             http.oauth2Client();
         }
    }
    

    Expose WebClient bean configured to use client credentials

    @Bean
    public OAuth2AuthorizedClientManager authorizedClientManager(
            ClientRegistrationRepository clientRegistrationRepository,
            OAuth2AuthorizedClientRepository authorizedClientRepository) {
    
        OAuth2AuthorizedClientProvider authorizedClientProvider =
                OAuth2AuthorizedClientProviderBuilder.builder()
                        .clientCredentials()
                        .build();
    
        DefaultOAuth2AuthorizedClientManager authorizedClientManager =
                new DefaultOAuth2AuthorizedClientManager(
                        clientRegistrationRepository, authorizedClientRepository);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
    
        return authorizedClientManager;
    }
    
    
    @Bean
    WebClient webClient(OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager) {
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
                new ServletOAuth2AuthorizedClientExchangeFilterFunction(
                        oAuth2AuthorizedClientManager);
        
        // default registrationId - Only if you are not using the webClient to talk to different external APIs
       oauth2Client.setDefaultClientRegistrationId("ei-gateway");
        
        return WebClient.builder()
          .apply(oauth2Client.oauth2Configuration())
          .build();
    }
    

    Now you can use WebClient in your code to access external protected resources.

    references:

    https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2client https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2Client-webclient-servlet https://docs.spring.io/spring-security/site/docs/current/reference/html5/#defaulting-the-authorized-client

    This set up worked for me when the application is not configured as a resource server, I had to use a different configuration when the application needs to use WebClient, but also configured to be a resource server.