Spring Boot 3 OAuth2 Client for non reactive project

Karanbir Singh
2 min readAug 19, 2023

Spring Boot 2.x.x sometime back had introduced a WebClient based OAuth2 Client & when we say WebClient that is Reactive & that does not works with a servlets based project out of the box in a go. You will get some errors for sure.

Reason for the error is the underneath framework to call token endpoint to resolve the access token should not be reactive.

So this blog post is dedicated to the very specific problem statement & the solution around the same. The blog post here majorly is code only. With a representational image for explanation.

Flow Diagram

Animation for the flow

Code

The code makes use of Spring Boot 3 + JDK17

There are mainly 3 components:-

1. The Main Server that acts as an OAuth2 client. https://github.com/krnbr/spring-boot-oauth2-servlets
2. Auth Server for generating/ managing the OAuth2 access tokens. https://github.com/krnbr/spring-oauth2-server
3. Resource Server that is protected by the OAuth2 access token. https://github.com/krnbr/spring-oauth2-resource

The main snippet for the config is as below:-

@Bean
public WebClient webClientTest(final @Value("${oauth2.registration.id}") String oauth2RegistrationId,
final @Value("${resource.base}") String resourceBase,
final ClientRegistrationRepository clientRegistrationRepository,
final RedisTemplate<String, CustomOAuth2AccessToken> redisDataTemplate,
final StringRedisTemplate stringRedisTemplate) {
var tokenEndpointLogger = LoggerFactory.getLogger(oauth2RegistrationId+OAUTH2_TOKEN_ENDPOINT);
var resourceEndpointLogger = LoggerFactory.getLogger(oauth2RegistrationId+RESOURCE_ENDPOINT);

var defaultClientCredentialsTokenResponseClient = new DefaultClientCredentialsTokenResponseClient();
defaultClientCredentialsTokenResponseClient
.setRestOperations(getRestTemplateForTokenEndPoint(oauth2RegistrationId, tokenEndpointLogger));

var provider = OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials(c -> c.accessTokenResponseClient(defaultClientCredentialsTokenResponseClient))
.build();

var oauth2AuthorizedClientService = new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);

var authorizedClientServiceOAuth2AuthorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, oauth2AuthorizedClientService);
authorizedClientServiceOAuth2AuthorizedClientManager.setAuthorizedClientProvider(provider);

var oauth = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientServiceOAuth2AuthorizedClientManager);
oauth.setDefaultClientRegistrationId(oauth2RegistrationId);

return WebClient.builder()
// base path of the client, just path while calling is required
.baseUrl(resourceBase)
.apply(oauth.oauth2Configuration())
.filter(logResourceRequest(resourceEndpointLogger, oauth2RegistrationId))
.filter(logResourceResponse(resourceEndpointLogger, oauth2RegistrationId))
.build();
}

// this function is the main part relating to the servlets based projects
private RestTemplate getRestTemplateForTokenEndPoint(String oauth2RegistrationId, Logger tokenEndpointLogger) {
var restTemplateForTokenEndPoint = new RestTemplate();
restTemplateForTokenEndPoint
.setMessageConverters(
List.of(new FormHttpMessageConverter(),
new OAuth2AccessTokenResponseHttpMessageConverter()
));
restTemplateForTokenEndPoint
.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
restTemplateForTokenEndPoint
.setInterceptors(List.of(restTemplateRequestInterceptor(tokenEndpointLogger, oauth2RegistrationId)));
return restTemplateForTokenEndPoint;
}

If you observe the code above, the main client for calling the resource is reactive i.e. WebClient only, but the underlying client to fetch access token, is based on RestTemplate.

--

--

Karanbir Singh

API developer + Web Application developer + Devops Engineer = Full Stack Developer