How to invoke OAuth2 protected microservice using WebClient in Spring Boot?

Let’s say you want to call an OAuth2 protected microservice from your Spring Boot microservice application.

Spring Boot as usual does majority of the work for us.

We just need to add a dependency ,some configuration and using a single HTTP call using Spring Web Client we can invoke the microservice.

Before that , to know how to protect a microservice using OAuth2 refer this post.

To invoke a OAuth2 protected resource follow these steps:

STEP 1: Add required dependencies

STEP 2: Add required configuration in application.yml

STEP 3: Build a custom WebClient

STEP 4: Test

STEP 1: Add required dependencies:

The following three dependencies are required :

	<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-oauth2-client</artifactId>
		</dependency>
	<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-webflux</artifactId>
		</dependency>

When you add spring-boot-starter-oauth2-client Spring will assume that you are going to make OAuth2 calls and so will expect certain configuration in application.yml.

The next step explores that:

STEP 2: Add required configuration in application.yml

To call an OAuth2 protected resource ,you need an access token.

This access token is appended in the header when you make the actual call to the protected resource.

To get this token you need to make a call to the authorization server.

This call will be made automatically by Spring. It will automatically append the access token in all the calls made by your WebClient.

For this ,you need to give the information to Spring to make the access token call.

You need to specify the following:

  • client id
  • client secret
  • authorization type (this will be a constant “client_credentials” as for microservices this is the preferred authorization type)
  • scope if you have configured it on the authorization server else you can leave this.
  • Token URL (this is the API which will generate the token)
  • Issuer URL (this is the authorization server url along with the realm info that you configure on the authorization server)

Here is a sample:



server:
  port: 8081

spring:
  security:
    oauth2:
      client:
        registration:
          mywebclient:
           client-id: myclient
           client-secret: Wna8BGXiGIhmnzr7T1UQKb2260ruZhWB
           authorization-grant-type: client_credentials
           
        provider:
          mywebclient:
            issuer-uri: http://localhost:8080/realms/myrealm
            token-uri: http://localhost:8080/realms/myrealm/protocol/openid-connect/token
            
          
            

In the above configuration “mywebclient” is the name we give to our oauth2 client. Rest all are predefined properties of Spring .We just need to feed the values.

You can obtain these values from your authorization server as explained step 6 in this post.

Once this configuration is done you need to build a WebClient spring bean with a filter.

This filter will filter all calls made by your WebClient and append an OAuth2 token to it.

This is explored in next step.

Advertisements

STEP 3: Build a custom Web Client

As earlier mentioned you need to add a filter to your webclient.

You can configure your web client centrally or for each REST API call you make you can add the filter.

In this post we will explore the former option.

Let’s do this step by step since the creation of a custom web client looks a bit complex:

We need a web client like this:

 WebClient.builder().apply(filter.oauth2Configuration()).build();

Notice that we are applying oauth2 configuration from a filter here.

Here is the filter to be used:

ServletOAuth2AuthorizedClientExchangeFilterFunction filter = new ServletOAuth2AuthorizedClientExchangeFilterFunction(
				authorizedClientManager);
		filter.setDefaultClientRegistrationId("mywebclient");

We also need to set the oauth2 client name which we configured in application.yml as shown above.

Now we have the filter.

Let’s create a WebClient bean:

	@Bean
	WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {

		ServletOAuth2AuthorizedClientExchangeFilterFunction filter = new ServletOAuth2AuthorizedClientExchangeFilterFunction(
				authorizedClientManager);
		filter.setDefaultClientRegistrationId("mywebclient");
		return WebClient.builder().apply(filter.oauth2Configuration()).build();
	}

The above method creates a WebClient bean.

Spring expects an OAuth2AuthorizedClientManager as a dependency.

To satisfy that create a client manager :


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

The above method takes two arguments which are automatically injected by Spring. You don’t need to define those beans.

The full configuration here:

package app.example;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
public class WebClientOAuth2Config {

	@Bean
	WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {

		ServletOAuth2AuthorizedClientExchangeFilterFunction filter = new ServletOAuth2AuthorizedClientExchangeFilterFunction(
				authorizedClientManager);
		filter.setDefaultClientRegistrationId("mywebclient");
		return WebClient.builder().apply(filter.oauth2Configuration()).build();
	}

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

Once this is configured we should be ready.

But here comes an issue ,this looks like a bug in Spring.

Since we added the spring-boot-starter-oauth2-client dependency Spring expects that your current microservice will also be protected by OAuth2. So if you create a REST API in your current microservice it will be automatically protected by OAuth2.

To disable this add a configuration which permits all requests to your client microservice:

package app.example;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

	@Override
	protected void configure(HttpSecurity http) throws Exception {

		http.authorizeRequests().antMatchers("*").permitAll();
	}

}

That’s it!

Now let’s test our changes.

Advertisements

STEP 4: Test

To test our changes let’s create a simple REST API .

We will call an oauth2 protected REST API from this API:

package app.example;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;

@RestController
public class TestController {

	
	@Autowired
	private WebClient webClient;
	
	@GetMapping("/test")
	public String test() {
	
		return webClient.get().uri("http://localhost:8085/test").retrieve().bodyToMono(String.class).block();
				
	}
}

As you notice I am invoking an API (http://localhost:8085/test – refer this post to create this API). This is protected by OAuth2. It just returns a string message “success”.

I have autowired WebClient. The custom web client we created will be injected here by Spring.

And the access token will be automatically appended!

I deployed the service on port 8081.

Here is the response:

It worked!

We just made a single call and Spring automatically got the access token and appended it to this call.

Here is the entire code:

https://github.com/vijaysrj/webclientoauth2

6 thoughts

  1. Thank you! We make stateless REST API calls using OAuth2 in microservices , so logout doesn’t come into picture here…there is no session maintained for you to logout a user

    Like

  2. Great content Vijay. Have a question. Does this approach internally call auth server for each and every request to get the bear token?

    Like

      1. Thank you for your response. Does this happen for EVERY request? Ideally I don’t want to make a call to auth server for every request. I would need to call the auth server once, cache the token for certain amount of time (until token’s expiration time) so that I don’t bombard auth server with too many calls.

        Hence, I would like to understand is there any caching mechanism with respect to caching the bearer token.

        Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s