DEV Community

ReLive27
ReLive27

Posted on • Edited on

Spring Security OAuth2 Client Credentials Grant

Overview

What to do when there is no clear resource owner, or the resource owner is indistinguishable to the client? This is a fairly common scenario. For example, when direct communication between backend systems is required. This article will introduce OAuth2.0 client credential authorization.

The OAuth2.0 documentation describes client credentials grant:

Clients use the client credentials grant type to obtain access tokens outside the context of a user. This is typically used by clients to access resources about themselves, rather than accessing resources about the user.

In this article, you'll learn about building OAuth2 client credential grants with Spring Security, allowing services to interoperate securely without authenticated users.

OAuth2 client credential authorization is more straightforward than authorization code authorization, and it is usually used for operations such as CRON tasks and other types of backend data processing.

Client credentials grant flow

When an application requests an access token to access other resources, authorization is done using client credentials, not on behalf of the user.

request parameters

grant_type(required)

The grant_type parameter must be set to client_credentials.

scope(optional)

Your service can support different scopes for client credential grants.

client authentication(required)

The client needs to authenticate this request. Typically, the service will allow additional request parameters client_id and client_secret, or accept the client ID and secret in the HTTP Basic auth header.

Image description

💡 Note: If you don’t want to read till the end, you can view the source code here.Don’t forget to give a star to the project if you like it!

OAuth2 authorization server

Here we use Spring Authorization Server to build an OAuth2 authorization server. I won't repeat the details here. You can refer to the article Using JWT with Spring Security OAuth2 to build an authorization server. Here only the difference from the previous authorization code grant process authorization service configuration is described.

Configuration

When we create a client using the RegisteredClient builder type, we will configure the client to support client credential authorization and simply store it in memory.



@Bean
public RegisteredClientRepository registeredClientRepository() {
  RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
    .clientId("relive-client")
    .clientSecret("{noop}relive-client")
    .clientAuthenticationMethods(s -> {
      s.add(ClientAuthenticationMethod.CLIENT_SECRET_POST);
      s.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
    })
    .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
    .redirectUri("http://127.0.0.1:8070/login/oauth2/code/messaging-client-model")
    .scope("message.read")
    .clientSettings(ClientSettings.builder()
                    .requireAuthorizationConsent(true)
                    .requireProofKey(false)
                    .build())
    .tokenSettings(TokenSettings.builder()
                   .accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED)
                   .idTokenSignatureAlgorithm(SignatureAlgorithm.RS256)
                   .accessTokenTimeToLive(Duration.ofSeconds(30 * 60))
                   .refreshTokenTimeToLive(Duration.ofSeconds(60 * 60))
                   .reuseRefreshTokens(true)
                   .build())
    .build();

  return new InMemoryRegisteredClientRepository(registeredClient);
}


Enter fullscreen mode Exit fullscreen mode

Above we configured an OAuth2 client and specified authorizationGrantType as client_credentials:

Build an OAuth2 resource server with Spring Security

The OAuth2 resource server configuration is consistent with the resource service set-up in this article Using JWT with Spring Security OAuth2, you can refer to the introduction of OAuth2 resource service in this article, or obtain the source code address of this article at the end of the article to view.

Configuration

The OAuth2 resource server provides a /resource/article protected endpoint and uses Spring Security to secure this service.



@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
  http.requestMatchers()
    .antMatchers("/resource/article")
    .and()
    .authorizeRequests()
    .mvcMatchers("/resource/article")
    .access("hasAuthority('SCOPE_message.read')")
    .and()
    .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
  return http.build();
}


Enter fullscreen mode Exit fullscreen mode

Please note that the OAuth2 resource service /resource/article endpoint requires "message.read" permission to access, Spring Security will add "SCOPE_" before the required scope name, so the actual required scope is "message.read" and Not "SCOPE_message.read".

Build an OAuth2 client with Spring Security

In this section, you will request resource services using the currently recommended WebClient, which is part of Spring's WebFlux package. This is Spring's reactive, non-blocking API, and you can read more about it in the Spring Documentation.

Under the CRON task defined by @Scheduled this annotation, you will use WebClient to make requests.

Maven dependencies



<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <version>2.6.7</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
  <version>2.6.7</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-oauth2-client</artifactId>
  <version>2.6.7</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webflux</artifactId>
  <version>5.3.9</version>
</dependency>
<dependency>
  <groupId>io.projectreactor.netty</groupId>
  <artifactId>reactor-netty</artifactId>
  <version>1.0.9</version>
</dependency>


Enter fullscreen mode Exit fullscreen mode

Configuration

We will configure OAuth2 authorization information in application.yml and specify the OAuth2 client service port number:



server:
  port: 8070

spring:
  security:
    oauth2:
      client:
        registration:
          messaging-client-model:
            provider: client-provider
            client-id: relive-client
            client-secret: relive-client
            authorization-grant-type: client_credentials
            client-authentication-method: client_secret_post
            scope: message.read
            client-name: messaging-client-model
        provider:
          client-provider:
            token-uri: http://127.0.0.1:8080/oauth2/token


Enter fullscreen mode Exit fullscreen mode

Next we will create a SecurityConfig class to configure the beans required by the Spring Security OAuth2 client:



@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
  http
    .authorizeRequests(authorizeRequests ->
                       authorizeRequests.anyRequest().permitAll()
                      )
    .oauth2Client(withDefaults());
  return http.build();
}

@Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
  ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
  return WebClient.builder()
    .filter(oauth2Client)
    .build();
}

@Bean
OAuth2AuthorizedClientManager authorizedClientManager(ClientRegistrationRepository clientRegistrationRepository,
                                                      OAuth2AuthorizedClientService authorizedClientService) {

  OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder
    .builder()
    .clientCredentials()
    .build();
  AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService);
  authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

  return authorizedClientManager;
}


Enter fullscreen mode Exit fullscreen mode

We create a WebClient instance to perform HTTP requests to the resource server, and add an OAuth2 authorization filter to the WebClient. AuthorizedClientServiceOAuth2AuthorizedClientManager This is the high-level controller class that coordinates OAuth2 client credential grant requests, here I will point out that AuthorizedClientServiceOAuth2AuthorizedClientManager is a class specifically designed to be used outside the context of an HttpServletRequest .

From the Spring Documentation:

The DefaultOAuth2AuthorizedClientManager is intended to be used in the context of an HttpServletRequest. When operating outside the context of an HttpServletRequest, use AuthorizedClientServiceOAuth2AuthorizedClientManager instead.

Next we will create a task defined using @Scheduled annotation, and inject WebClient to call resource service request:



@Service
public class ArticleJob {

  @Autowired
  private WebClient webClient;

  @Scheduled(cron = "0/2 * * * * ? ")
  public void exchange() {
    List list = this.webClient
      .get()
      .uri("http://127.0.0.1:8090/resource/article")
      .attributes(clientRegistrationId("messaging-client-model"))
      .retrieve()
      .bodyToMono(List.class)
      .block();
    log.info("Call resource server execution result:" + list);
  }
}



Enter fullscreen mode Exit fullscreen mode

The client service will initiate a request every 2 seconds and print the result on the console.

Conclusion

As always, the source code used in this article is available on GitHub.

You might want to read on to the next one:

Thanks for reading!

Top comments (0)