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.
💡 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);
}
Above we configured an OAuth2 client and specified authorizationGrantType as client_credentials:
- clientId: relive-client
- clientSecret: relive-client
- redirectUri: http://127.0.0.1:8070/login/oauth2/code/messaging-client-model
- scope: message.read
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();
}
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>
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
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;
}
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);
}
}
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)