javaspring-bootspring-securityspring-security-oauth2

Spring Security 5 Replacement for OAuth2RestTemplate


In spring-security-oauth2:2.4.0.RELEASE, classes such as OAuth2RestTemplate, OAuth2ProtectedResourceDetails and ClientCredentialsAccessTokenProvider have all been marked as deprecated.

From the javadoc on these classes, it points to a spring security migration guide that insinuates that people should migrate to the core spring-security 5 project. However, I'm having trouble finding how I would implement my use case in this project.

All of the documentation and examples talk about integrating with a 3rd part OAuth provider if you want incoming requests to your application to be authenticated and you want to use the 3rd party OAuth provider to verify the identity.

In my use case, all I want to do is make a request with a RestTemplate to an external service that is protected by OAuth. Currently, I create an OAuth2ProtectedResourceDetails with my client id and secret which I pass into an OAuth2RestTemplate. I also have a custom ClientCredentialsAccessTokenProvider added to the OAuth2ResTemplate that just adds some extra headers to the token request that are required by the OAuth provider I'm using.

In the spring-security 5 documentation, I've found a section that mentions customising the token request, but again that looks to be in the context of authenticating an incoming request with a 3rd party OAuth provider. It is not clear how you would use this in combination with something like a ClientHttpRequestInterceptor to ensure that each outgoing request to an external service first gets a token and then gets that added to the request.

Also, in the migration guide linked above, there is reference to an OAuth2AuthorizedClientService which it says is useful for using in interceptors. But again, this looks like it relies on things like the ClientRegistrationRepository which seems to be where it maintains registrations for third party providers if you want to use that provide to ensure an incoming request is authenticated.

Is there any way I can make use of the new functionality in spring-security 5 for registering OAuth providers in order to get a token to add to outgoing requests from my application?


Solution

  • OAuth 2.0 Client features of Spring Security 5.2.x do not support RestTemplate, but only WebClient. See Spring Security Reference:

    HTTP Client support

    • WebClient integration for Servlet Environments (for requesting protected resources)

    In addition, RestTemplate will be deprecated in a future version. See RestTemplate javadoc:

    NOTE: As of 5.0, the non-blocking, reactive org.springframework.web.reactive.client.WebClient offers a modern alternative to the RestTemplate with efficient support for both sync and async, as well as streaming scenarios. The RestTemplate will be deprecated in a future version and will not have major new features added going forward. See the WebClient section of the Spring Framework reference documentation for more details and example code.

    Therefore, the best solution would be to abandon RestTemplate in favor of WebClient.


    Using WebClient for Client Credentials Flow

    Configure client registration and provider either programmatically or using Spring Boot auto-configuration:

    spring:
      security:
        oauth2:
          client:
            registration:
              custom:
                client-id: clientId
                client-secret: clientSecret
                authorization-grant-type: client_credentials
            provider:
              custom:
                token-uri: http://localhost:8081/oauth/token
    

    …​and the OAuth2AuthorizedClientManager @Bean:

    @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;
    }
    

    Configure the WebClient instance to use ServerOAuth2AuthorizedClientExchangeFilterFunction with the provided OAuth2AuthorizedClientManager:

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

    Now, if you try to make a request using this WebClient instance, it will first request a token from the authorization server and include it in the request.