In the Client Credentials Resource Server, we learned how to config Spring Boot Resource Server with Keycloak authorization server for Client Credentials Grant Type using Token Signature Resource Server and we used Postman as the a client to get access token and use it to call to the Spring Boot resource server manually.
In this section, we will continue to configure a Spring Boot client server which will connect to Keycloak authorization server to get token and call to Resource server automatically when the user call to client server to get the data.
So firstly, we need to overview the simple diagram that we are going to do as in the image below.
So as you can see, when the user use Postman to call to the Spring Boot Client application then it will automatically call to the Keycloak authorization server with the client credentials to get the access token and then call to the Spring Boot Resource Server application to get the data and return to the Postman.
For setting up Keycloak authorization server please view Keycloak Setup with Client Credentials Grant Type.
Before going the implementation details, we should take a look through the core interfaces/classes of the spring-security-oauth2-client dependencies which we will use in the Spring Boot service to create Spring Boot client server with Keycloak. Then later, we will know what are we configuring and why do we need to configure them.
The dependency for Spring Boot service will look like as below:
pom.xml
1 2 3 4 5 6 7 8 910
.....
<!-- Spring Boot oauth2 client --><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-client</artifactId><version>5.6.0</version></dependency>
.....
So in the spring-security-oauth2-client dependencies there are 6 main interfaces/classes.
ClientRegistration is a representation of a client registered with an OAuth 2.0 or OpenID Connect 1.0 Provider.
A client registration holds information, such as client id, client secret, authorization grant type, redirect URI, scope(s), authorization URI, token URI, and other details.
ClientRegistration and its properties are defined as follows:
The ID that uniquely identifies the ClientRegistration. Ex: keycloak, okta .etc..
clientId
The client identifier.
clientSecret
The client secret.
clientAuthenticationMethod
The method used to authenticate the Client with the Provider. The supported values are basic, post and none (public clients).
authorizationGrantType
The OAuth 2.0 Authorization Framework defines four Authorization Grant types. The supported values are authorization_code, client_credentials and password.
redirectUri
The client’s registered redirect URI that the Authorization Server redirects the end-user’s user-agent to after the end-user has authenticated and authorized access to the client.
scopes
The scope(s) requested by the client during the Authorization Request flow, such as openid, email, or profile.
clientName
A descriptive name used for the client. The name may be used in certain scenarios, such as when displaying the name of the client in the auto-generated login page.
authorizationUri
The Authorization Endpoint URI for the Authorization Server.
tokenUri
The Token Endpoint URI for the Authorization Server.
jwkSetUri
The URI used to retrieve the JSON Web Key (JWK) Set from the Authorization Server, which contains the cryptographic key(s) used to verify the JSON Web Signature (JWS) of the ID Token and optionally the UserInfo Response.
issuerUri
Returns the issuer identifier uri for the OpenID Connect 1.0 provider or the OAuth 2.0 Authorization Server.
configurationMetadata
The OpenID Provider Configuration Information. This information will only be available if the Spring Boot 2.x property spring.security.oauth2.client.provider.{{providerId}}.issuerUri is configured.
(userInfoEndpoint)uri
The UserInfo Endpoint URI used to access the claims/attributes of the authenticated end-user.
(userInfoEndpoint)authenticationMethod
The authentication method used when sending the access token to the UserInfo Endpoint. The supported values are header, form and query.
userNameAttributeName
The name of the attribute returned in the UserInfo Response that references the Name or Identifier of the end-user.
Client registrations are typically loaded automatically from an application.yml/application.properties file. Spring auto-configuration looks for properties with the schema spring.security.oauth2.client.registration.[registrationId] and creates a ClientRegistration instance within a ClientRegistrationRepository. See the example configuration in application.yml below.
application.yml
1 2 3 4 5 6 7 8 91011121314
spring:security:oauth2:client:registration:keycloak:# <--- It's your custom client. I am using keycloakclient-id:myclientclient-secret:80YlNd6qwz84hnisibF8QHVAtjUkVj0pauthorization-grant-type:client_credentialsscope:# your scopes-openid-address-email-profile
Below is the table that contains all configuration that we can apply in to application.yml/application.properties for ClientRegistration configurations.
The ClientRegistrationRepository serves as a repository for OAuth 2.0 / OpenID Connect 1.0 ClientRegistration(s). ClientRegistrationRepository is a container class that holds ClientRegistrations.
Client registration information is ultimately stored and owned by the associated Authorization Server. This repository provides the ability to retrieve a sub-set of the primary client registration information, which is stored with the Authorization Server.
OAuth2AuthorizedClient: Represents an authorized client. This is a composed class that contains a client registration but adds authentication information. Take a look at the code excerpt below. You’ll see that the OAuth2AuthorizedClient adds three properties composed on top of the client registration: a principal name, an access token, and a refresh token.
OAuth2AuthorizedClientRepository: is a container class that holds and persists authorized clients between requests. The default implementation, InMemoryOAuth2AuthorizedClientService, simply stores the clients in memory.
OAuth2AuthorizedClientManager: is the manager class that contains the logic to handle the authorization flow. Most importantly, it authorizes and re-authorizes OAuth 2.0 clients using an OAuth2AuthorizedClientProvider. It also delegates persistence of the authorized clients and calls success or failure handlers when client authorization succeeds or fails.
OAuth2AuthorizedClientProvider: represents an OAuth 2.0 provider and handles the actual request logic for different grant types and OAuth 2.0 providers. They can be auto-configured based on property values (spring.security.oauth2.client.provider.[provider name]). In the case of this tutorial, we will be using Keycloak as our provider, so we’ll see properties with the prefix spring.security.oauth2.client.provider.keycloak. that are auto-configuring an associated OAuth2AuthorizedClientProvider.
application.yml
1 2 3 4 5 6 7 8 9101112131415161718
spring:security:oauth2:client:registration:keycloak:# <--- It's your custom client. I am using keycloak client-id:myclientclient-secret:80YlNd6qwz84hnisibF8QHVAtjUkVj0pauthorization-grant-type:client_credentialsscope:# your scopes -openid-address-email-profileprovider:keycloak:# <--- Here Registered my custom provider authorization-uri:http://localhost:8080/auth/realms/myrealm/protocol/openid-connect/authtoken-uri:http://localhost:8080/auth/realms/myrealm/protocol/openid-connect/token
<dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>2021.0.0</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><dependencies><!--spring boot starter--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.6.3</version></dependency><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.22</version><scope>provided</scope></dependency><!--Spring cloud openfeign--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId><version>3.1.0</version></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId><version>3.1.0</version></dependency><!-- Spring security --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId><version>2.6.1</version></dependency><!-- Spring Boot oauth2 client --><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-client</artifactId><version>5.6.0</version></dependency></dependencies>
To make Spring Boot application become client server we need to apply spring-boot-starter-security and spring-security-oauth2-client dependencies and also spring-cloud-starter-openfeign to make the call with the access token from the client to the resource server, this is the service to service communication.
We will create the OAuth2AuthorizedClientManager bean by new AuthorizedClientServiceOAuth2AuthorizedClientManager and we need to inject 2 beans which are ClientRegistrationRepository and OAuth2AuthorizedClientService.
Next, let's create a service class OAuth2ClientService.java with a method for getting the access token from the Keycloak authorization server as below.
packagecom.springboot.cloud.openfeign.client.credentials.interceptor.config.oauth2.v1;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.security.authentication.AnonymousAuthenticationToken;importorg.springframework.security.core.Authentication;importorg.springframework.security.core.authority.AuthorityUtils;importorg.springframework.security.oauth2.client.OAuth2AuthorizeRequest;importorg.springframework.security.oauth2.client.OAuth2AuthorizedClient;importorg.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;importorg.springframework.stereotype.Service;import staticjava.util.Objects.isNull;@Slf4j@ServicepublicclassOAuth2ClientService{privatefinalOAuth2AuthorizedClientManageroAuth2AuthorizedClientManager;privatestaticfinalAuthenticationANONYMOUS_USER_AUTHENTICATION=newAnonymousAuthenticationToken("key","anonymous",AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));@AutowiredpublicOAuth2ClientService(OAuth2AuthorizedClientManageroAuth2AuthorizedClientManager){this.oAuth2AuthorizedClientManager=oAuth2AuthorizedClientManager;}publicStringgetAccessToken(StringclientRegistrationId){OAuth2AuthorizeRequestoAuth2AuthorizeRequest=OAuth2AuthorizeRequest.withClientRegistrationId(clientRegistrationId).principal(ANONYMOUS_USER_AUTHENTICATION).build();OAuth2AuthorizedClientclient=oAuth2AuthorizedClientManager.authorize(oAuth2AuthorizeRequest);if(isNull(client)){thrownewIllegalStateException("client credentials flow on "+clientRegistrationId+" failed, client is null");}return"Bearer "+client.getAccessToken().getTokenValue();}}
So, in this OAuth2ClientService, we will use the OAuth2AuthorizedClientManager to authorize with the Keycloak authorization server with an OAuth2AuthorizeRequest and receive the OAuth2AuthorizedClient, then from this OAuth2AuthorizedClient we can extract the access token.
Finally, we also need to put some configurations in the application.yml for the spring security oauth2 client.
server:port:8086spring:security:oauth2:url:http://localhost:8085client:registration:keycloak:# <--- It's your custom client. I am using keycloakclient-id:myclientclient-secret:80YlNd6qwz84hnisibF8QHVAtjUkVj0pauthorization-grant-type:client_credentialsscope:openid, address, email, profile# your scopesprovider:keycloak:# <--- Here Registered my custom providerauthorization-uri:http://localhost:8080/auth/realms/myrealm/protocol/openid-connect/authtoken-uri:http://localhost:8080/auth/realms/myrealm/protocol/openid-connect/tokenlogging:level:com.springboot.cloud.openfeign.client.credentials.interceptor.api:DEBUGfeign:client:config:default:loggerLevel:full
So in this config we will use the OAuth2ClientService to get the access token and put it into the Authorization header of requests to Spring Boot Resource Server.
Next, we also need to create an adapter interface name SpringOAuth2ResourceClient to configure FeignClient with target api as below:
We will put the FeignClient configuration class that we created in the step above into this FeignClient by using param configuration. So now, every request of the api /v1/card to the Spring Boot resource server will have the access token which is fetched from the Keycloak authorization server.
Finally, in the main class, we just need to put annotation @EnableFeignClients as below.
We will increase the security for our Spring Boot client service a little bit. In detail, to call the api in the controller of the step above, the user from Postman must put a basic authentication with username and password.
So let's create a SecurityConfig.java class and put some basic authentication configuration as below.
Note: The properties under spring.security.user won't work because Spring Boot will back off creating the UserDetailService bean. So, we will have to define the UserDetailsService bean by our self.
Now, let's use Postman to call to api /v1/oauth2/auth/interceptor/card of our Spring Boot Client server, then you can receive the data of Sping Boot Resource server successfully as below.
If you look into the our Spring Boot Client service then you can see the access token is added into the Authorization header of the request to the Spring Boot Resource server as below.
It means, our implementation is successful.
Now, if you make more calls from Postman, you can see the access token has not changed, so our Spring Boot Client server had reused the access token because it is still not expired.
Then if you wait a little bit and make a call from Postman again, then you can see in the log we will have a new access token. So it means the old access token has been expired so our Spring Boot Client server will call to Keycloak authorization server to get the access token again.