Search code examples
javaspringspring-bootspring-webfluxspring-boot-starter-oauth2-client

Spring Boot Oauth2 Client(Reactive) Mutual TLS/SSL token uri


Spring boot 2.3.x and Spring 5.x had recently added the support for configuring the reactive oauth2 client based on the WebClient class.

I had a requirement for the Client Credentials grant flow configuration

Doing this call without the Mutual TLS/SSL is quiet straight forward.

Normal (without TLS/SSL) configuration (@Configuration) the code extract is as below:-

@Bean
public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
        ReactiveClientRegistrationRepository clientRegistrationRepository,
        ServerOAuth2AuthorizedClientRepository authorizedClientRepository){

    ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
            .clientCredentials()
            .build();

    DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientRepository);

    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
    return authorizedClientManager;
}

@Bean("testClient")
public WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager,
                           @Value("${test.client.base.url}") String baseUrl) {
    ServerOAuth2AuthorizedClientExchangeFilterFunction oauthFunction = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
    oauthFunction.setDefaultClientRegistrationId("local");
    return WebClient.builder()
            .baseUrl(baseUrl)
            .filter(oauthFunction)
            .build();
}

@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
    http.oauth2Client();
    return http.build();
}

The properties file

spring.security.oauth2.client.registration.local.authorization-grant-type=client_credentials
spring.security.oauth2.client.registration.local.client-id=client_id
spring.security.oauth2.client.registration.local.client-secret=client_secret

spring.security.oauth2.client.provider.local.token-uri=http://hostname:port/oauth/token
test.client.base.url=http://protected-resource/v1/apis

But calling the oauth2 authorization server over Mutual TLS(Client cert) was a big deal.

How to do that? I would like to share the same with the community and would answer the same myself below


Solution

  • The answer to the requirement and the main change will be in the bean authorizedClientManager

    The scope of the answer is the client credentials grant flow only though changes for the other oauth2 grant flows should be similar and this would help there too.

    @Bean
    public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
            ReactiveClientRegistrationRepository clientRegistrationRepository,
            ServerOAuth2AuthorizedClientRepository authorizedClientRepository){
    
        // construct client credential token response client yourself
        WebClientReactiveClientCredentialsTokenResponseClient accessTokenResponseClient = new WebClientReactiveClientCredentialsTokenResponseClient();
    
        // construct the sslContext as per your needs and inject in below
        // and create httpClient by injecting your sslContext here
        HttpClient httpClient = HttpClient.create()
                .tcpConfiguration(client -> client.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000))
                .secure(sslContextSpec -> sslContextSpec.sslContext(sslContext));
    
        ClientHttpConnector httpConnector = new ReactorClientHttpConnector(httpClient);
    
        accessTokenResponseClient.setWebClient(WebClient.builder().clientConnector(httpConnector).build());
    
        ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder
                .builder()
                .clientCredentials(c -> {
                    c.accessTokenResponseClient(accessTokenResponseClient);
                }).build();
    
        DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientRepository);
    
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
        return authorizedClientManager;
    }
    

    Here if you see the line .secure(sslContextSpec -> sslContextSpec.sslContext(sslContext));

    You need to construct the sslContext and inject the same, and that would depend entirely on your code setup.

    For detailed code and instructions you can go to the link here