Skip to content

Easy Spring Rest Client w/ OAuth2

Published: at 08:00 AM

Brief

My goal is to make posts like this the SIMPLEST place on the internet to learn how to do things that caused me trouble. That way, if this is found, someone doesn’t have to do the same digging I had to do.

What is OAuth2?

OAuth2 is a popular authorization framework that allows users to grant third-party applications access to their data without revealing their credentials. It’s often used for services like social media logins, API integrations, and more.

Key Components of OAuth2

How OAuth2 Works

Why RestClient?

While RestTemplate has been a staple for many years, its limitations and the introduction of more modern alternatives have led to its deprecation in recent versions of Spring. Let’s dive into the key differences between WebClient and RestClient and why RestTemplate is being phased out.

WebClient is built on top of Project Reactor, a reactive programming framework. This means it can handle asynchronous operations efficiently, making it well-suited for scenarios where concurrent requests and non-blocking I/O are essential.

However, with RestTemplate’s deprecation, the only real Spring alternative is WebClient. This requires including the spring-webflux dependencies and calling .block() when making blocking API calls. It feels shoe-horned into place.

In comes RestClient, a client written in the same functional style as WebClient, but supports synchronous and asynchronous operations out of the box. This lets us remove the spring-webflux dependency and use spring-web-mvc as the primary HTTP dependency for server and client applications.

The Setup

Here’s everything you need to get RestClient working with OAuth2!

build.gradle

plugins {
    id 'org.springframework.boot' version '3.4.0'
}

    // ... The rest of the stuff, this is just what's required

    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-security'
        implementation 'org.springframework.boot:spring-boot-starter-web'
        implementation 'org.springframework.security:spring-security-oauth2-client'
    }
}

application.yaml

spring:
  security:
    oauth2:
      client:
        registration:
          my-oauth-client:
            client-id: ${oauthClientId}
            client-secret: ${oauthClientSecret}
            provider: my-oauth-provider
            authorization-grant-type: client_credentials
            scope: openid
        provider:
          my-oauth-provider:
            token-uri: ${oauth2ServerUri}/protocol/openid-connect/token
            issuer-uri: ${oauth2ServerUri}

application-local.yaml

oauth2ServerUri: http://myServerUri:9090
oauthClientId: clientId
oauth2ClientSecret: mySecretSecret

RestClientConfiguration.java using the new OAuth2ClientHttpRequestInterceptor

@Configuration
public class RestClientConfiguration
{
    // This needs to match the YAML configuration
    private static final String CLIENT_REGISTRATION_ID = "my-oauth-client";

    @Bean
    public OAuth2AuthorizedClientManager authorizedClientManager (
        ClientRegistrationRepository clientRegistrationRepository,
        OAuth2AuthorizedClientService authorizedClientService
    ){
        // We create a manager using the autowired clientRegistrations from YAML and connect it to the service
        AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
            new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService);
        
        // Setting the clientManager to look for a clientCredentials configuration
        authorizedClientManager.setAuthorizedClientProvider(OAuth2AuthorizedClientProviderBuilder.builder()
            .clientCredentials()
            .build());
        return authorizedClientManager;
    }

    @Bean
    public RestClient oauth2RestClient(
        OAuth2AuthorizedClientManager authorizedClientManager) {

        // This is the new class! 
        // We instantiate a new interceptor to load into RestClient
        OAuth2ClientHttpRequestInterceptor oAuth2ClientHttpRequestInterceptor =
            new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
        // Then provide it the client registration to resolve the id from
        oAuth2ClientHttpRequestInterceptor.setClientRegistrationIdResolver(clientRegistrationIdResolver());

        // From here we simply return the client with any custom configuration, and we're good to go!
        return RestClient.builder()
            .baseUrl("http://myBaseUrl:8080")
            .requestInterceptor(oAuth2ClientHttpRequestInterceptor)
            .build();
    }

    private static OAuth2ClientHttpRequestInterceptor.ClientRegistrationIdResolver clientRegistrationIdResolver() {
     return (request) -> CLIENT_REGISTRATION_ID;
    }
}

Bonus: Setting up HttpServiceProxyFactory (not required but useful!)

HttpServiceProxyFactory is new in Spring 6!

This factory allows you to easily generate reactive proxies for HTTP services, providing a convenient and efficient way to interact with REST APIs. You can define your HTTP service interface, annotate it with appropriate HTTP method and path annotations, and the factory will automatically generate a proxy that implements the interface. This eliminates the need for manual configuration and reduces boilerplate code.

We can seamlessly inject our RestClient (or an existing WebClient) into the proxy, cutting back on the boilerplate and making the code much more readable!

public interface MyHttpService {

    @PostExchange("api/my/path")
    SomeResponse post(@RequestBody MyPostBody request);
}
@Configuration
public class HttpServiceFactory
{
    @Bean
    public MyHttpService getMyHttpService(RestClient oauth2RestClient) {
        // We're simply injecting our restClient into the factory and creating a concrete instance of the interface
        HttpServiceProxyFactory factory = HttpServiceProxyFactory
            .builderFor(RestClientAdapter.create(oauth2RestClient))
            .build();
        return factory.createClient(MyHttpService.class);
    }
}

Another Bonus: Setting up Logbook

The logbook library (https://github.com/zalando/logbook) can be integrated into this layout!

You can set up logbook by following this post: https://stevenpg.com/posts/request-body-with-spring-webclient/

From there, we just need to update our oauth2RestClient

Here’s the updated code below for easy access!

@Configuration
public class RestClientConfiguration
{
    // This needs to match the YAML configuration
    private static final String CLIENT_REGISTRATION_ID = "my-oauth-client";

    // ...

    @Bean
    public RestClient oauth2RestClient(
        OAuth2AuthorizedClientManager authorizedClientManager,
        LogbookRestClientInterceptor logbookRestClientInterceptor) {

        // This is the new class!!! We instantiate a new one and provide it the client registration to match
        OAuth2ClientHttpRequestInterceptor oAuth2ClientHttpRequestInterceptor =
            new OAuth2ClientHttpRequestInterceptor(authorizedClientManager, request -> CLIENT_REGISTRATION_ID);

        // From here we simply return the client with any custom configuration, and we're good to go!
        return RestClient.builder()
            .baseUrl("http://myBaseUrl:8080")
            .requestInterceptors(List.of(logbookRestClientInterceptor, oAuth2ClientHttpRequestInterceptor))
            .build();
    }
}

Summary

The new RestClient is already a popular alternative for developers in the Spring ecosystem. The lack of an OAuth2 component has been a sore spot for new users converting over from WebClient. So with this new feature releasing with Spring Boot 3.4.0, it can now take it’s rightful place as the default, non-webflux HTTP Client for Spring MVC!

Paired with the HttpServiceProxyFactory feature, making REST calls with OAuth2 authorization has never been easier. Pair that with the extensive autoconfiguration of logbook, and you have a powerful combination of security, readability and logging!


Previous Post
Jdk 23 Streams - How To Use Gather Operations
Next Post
Spring Cloud Stream's RecordRecoverableProcessor for Workflow Error Handling