In the Keycloak Setup, we learned how to set up Keycloak authorization server for Password Grant Type . Now, 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 user credentials (username and password) to get the access token and then call to the Spring Boot Resource Server application to get the data and return to the Postman.
So it will be different a little with the Client Credentials Grant Type because when the Spring Boot client server will use the user credentials which are users created in Realms of Keycloak authorization server instead of credentials of created clients.
For setting up Keycloak authorization server please view Keycloak Setup with Client Credentials Grant Type.
For Resource Server setup, you can view Client Credentials Resource Server, the resource server for Client Credential Grant Type, Password Grant Type and Authorization Code Grant Type are almost the same, so we can reuse it.
<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>
Like dependencies that we used for creating Client Credentials Client Server. 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.
So Let's create a Feign interceptor config in which we will try to get the access token and set it to the request header to call to the resource server.
So as you can see firstly, we will try to create and config the bean authorizedClientManager in the method authorizedClientManager();
To create and configure authorizedClientManager for the password grant type:
we have to create an OAuth2AuthorizedClientProvider with password grant and refreshToken to make sure our client will fetch new access token automatically when the old token is expired. Then we set OAuth2AuthorizedClientProvider to the authorizedClientManager.
Then we also need to set mapping attributes into the OAuth2AuthorizationContext which are the username/password of a Realm in Keycloak server. They will be used by the OAuth2AuthorizationContext to authorize (or re-authorize) the client identified by the provided clientRegistrationId.
The username and password of the user are load manually from the application.yml because the Password Grant Type is deprecated.
server:port:8086spring:security:oauth2:url:http://localhost:8087client:registration:keycloak:# <--- It's your custom client. I am using keycloakclient-id:passwordgrantauthorization-grant-type:passwordscope:openid, address, email, profile# your scopesusername:userpassword:userprovider: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.security.spring.security.oauth.client.server.password.grant.type.api:DEBUGfeign:client:config:default:loggerLevel:full
Next, after finishing OAuthClientCredentialsFeignManager config, then we will use it and clientRegistrationRepository to get the access token from the Keycloak authorization server. But implementing the function for getting access token we will put it into a class OAuthClientCredentialsFeignManager to make the code look better. So in the OAuthClientCredentialsFeignManager we will have a method as below.
packagecom.springboot.security.spring.security.oauth.client.server.password.grant.type.config;importlombok.extern.slf4j.Slf4j;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.security.oauth2.client.registration.ClientRegistration;importorg.springframework.security.oauth2.client.registration.ClientRegistrationRepository;import staticjava.util.Objects.isNull;@Slf4jpublicclassOAuthClientCredentialsFeignManager{privatestaticfinalAuthenticationANONYMOUS_USER_AUTHENTICATION=newAnonymousAuthenticationToken("key","anonymous",AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));privatefinalOAuth2AuthorizedClientManageroAuth2AuthorizedClientManager;privatefinalClientRegistrationRepositoryclientRegistrationRepository;publicOAuthClientCredentialsFeignManager(OAuth2AuthorizedClientManageroAuth2AuthorizedClientManager,ClientRegistrationRepositoryclientRegistrationRepository){this.oAuth2AuthorizedClientManager=oAuth2AuthorizedClientManager;this.clientRegistrationRepository=clientRegistrationRepository;}publicStringgetAccessToken(StringclientRegistrationId){try{ClientRegistrationclientRegistration=clientRegistrationRepository.findByRegistrationId(clientRegistrationId);OAuth2AuthorizeRequestoAuth2AuthorizeRequest=OAuth2AuthorizeRequest.withClientRegistrationId(clientRegistration.getRegistrationId()).principal(ANONYMOUS_USER_AUTHENTICATION).build();OAuth2AuthorizedClientclient=oAuth2AuthorizedClientManager.authorize(oAuth2AuthorizeRequest);if(isNull(client)){thrownewIllegalStateException("client credentials flow on "+clientRegistration.getRegistrationId()+" failed, client is null");}return"Bearer "+client.getAccessToken().getTokenValue();}catch(Exceptionexp){log.error("client credentials error "+exp.getMessage());thrownewIllegalArgumentException("client credentials error "+exp.getMessage(),exp);}}}
As you can see, we will use ClientRegistrationRepository to get the the ClientRegistration and use to to build the OAuth2AuthorizeRequest. Then the OAuth2AuthorizeRequest will be used by oAuth2AuthorizedClientManager to authorize the client. After client is authorized then we can get the access token from OAuth2AuthorizedClient by function .getAccessToken().getTokenValue(); and this token will be used for Feign Interceptor.
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/password-grant-type/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 and 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.