In this tutorial we'll see how to secure your Spring Boot microservices using Keycloak as Identity and access management server, OUTH2 for authorization and OpenID Connect which verifies the identity of users based on the authentication performed by an authorization server. Rather than securing individual microservices it is better to perform security at the API gateway level, making gateway as a single implementation point to provide cross cutting concerns to all the microservices such as security.
Using Keycloak
By using Keycloak you can have a separate authorization server for authentication and authorization, which means keeping your security logic separate from business logic and delegating all the security related tasks to the Keycloak server which acts as an IAM.
Install keycloak
Convenient way to use keycloak is to run it in a Docker container by using the following command. You can read more about it here.
docker run -d -p 7080:8080 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:26.0.5 start-dev
Exposed port is kept as 7080 as 8080 is usually already in use if you are using Spring Boot REST API with services deployed in Tomcat.
Username and password both are given as admin.
You can also download and install it in your local system. Get more information about it here.Start admin console
Once started you can access Keycloak using the URL- http://localhost:7080. It takes you to the login page where you can login using the credentials as admin. Once logged in you are directed to the admin console.
Here you need to configure Keycloak to perform authentication and authorization. Note that with this setup the configuration done is temporary (kept in memory) which will be lost once you logout. To persist your configuration, you can use a DB server to store it.
Create realm
First thing is to create a realm, on the left-hand side you should see a dropdown with master as the current realm. Click the dropdown and then select "Create realm".
Realm helps in grouping applications and users with in a realm and keeping it isolated. Keycloak suggests not to create clients and users with in Master realm. Use Master realm only for managing Keycloak not any other application. So, create a new realm and give a Realm name, I have given name as microservice-realm.
Once realm is created that becomes the default realm.
Create client
In order for an application or service to utilize Keycloak it has to register a client in Keycloak. Click on "Clients" option from the menu and select client type as "Open ID Connect".
For Client ID give a name for your client application. Same way enter name and description.
Click next.
Ensure client authentication is turned on. In the authentication flow select "Standard Flow" which is Authorization Code Grant type. Uncheck any other authentication flow option.
Click next.
The Valid redirect URIs, is a valid URI, browser can redirect to after a successful login. Since we are going to use Postman and there is no UI application as such so for now you can use * to mean any URI. For testing purpose you can use "*" as value for POst Logout Redirect URI and Web Origins.
Click save to save the new client.
If you go to the credentials tab you should be able to see how client is authenticated and what is client secret. We'll need the client's name and secret when trying to access the REST endpoint.
Create user with password
Select Users from the left-hand side section and then click on create new user. User is needed to authenticate against the Keycloak server.
With in the general section give value for Username and email. I have given as test_user and testuser@test.com. Then give values for First name and Last name. Also set the Email Verified to ON signifying that the user is already verified. Click on Create.
Select Credentials to set password for the created User. Enter value for password and set temporary to off so that you are not asked to change password later.
Same way create another user test_admin with its own password.
Create realm roles
Select Realm roles from the left-hand side section and enter values for the role. I have created two roles user and admin.
Mapping role with user
Go back to users and select test_user. Click on "Role Mapping" tab and then click "Assign role" button.
In the filter select filter by realm roles. Select "user" role for test_user.
Same way select "admin" role for test_admin user.
With that our Keycloak configuration is complete. Just to reiterate what we have done is-
- Created a realm.
- Created a client with client secret.
- Created a user.
- Created roles which are then mapped with the user.
Keycloak Authorization Code Grant Type flow
Oauth 2.0 specification defines several grant types which refers to the way IDToken/Access Token is granted. Some of the grant types are-
- Implicit Grant
- Authorization Code Grant
- Client Credentials Grant
- Device Authorization Grant
In this example we are using the Authorization Code Grant type which is optimized for confidential clients (Web Applications) and it is a redirection-based flow. In authorization code grant type first an authorization code is issued by authorization server using which an access token is requested from the authorization server.
OAuth roles in this scenario
OAuth specification defines the following roles-
- Resource server- It is the server that hosts the protected resources. Resource server accepts and responds to protected resource requests using access tokens.
- Client- It is an application which makes protected resource requests on behalf of the resource owner and with its authorization.
- Authorization server- The server issuing access tokens to the client after successfully authenticating the resource owner and obtaining authorization.
- Resource owner- Resource owner (typically an end-user) owns the resource and capable of granting access to a protected resource.
In our example gateway server acts as both client and resource server, Keycloak is the Authorization server.
Code changes
Refer this post Spring Boot Microservice + API Gateway + Resilience4J for getting the code to configure API Gateway in Microservices using Spring Cloud Gateway and the other microservices. We'll be adding security layer on top of that.
Adding dependencies
Need to add the following 3 Spring Boot starters for security in the gateway service.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-resource-server</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-jose</artifactId> </dependency>
Changes in configuration
The Spring OAuth2 Resource Server need to verify incoming JWT tokens for that we need to configure the JSON Web Key Set (JWKS) endpoint. With in the gateway-service open the application.yml and add the following configuration.
spring: security: oauth2: resourceserver: jwt: jwk-set-uri: "http://localhost:7080/realms/microservice-realm/protocol/openid-connect/certs"
You can get jwk-set-uri by going to Keycloak admin console, there go to "Realm Settings" and all the way down in the displayed screen. There click on OpenID Endpoint Configuration. There take the value for the jwks_uri property.
Adding Security configuration class
Create a Configuration class annotated with @Configuration because we need to declare @Bean methods. Also use @EnableWebFluxSecurity to add Spring Security WebFlux support. WebFlux because Spring Cloud Gateway is built using the reactive version of the Spring web module.
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter; import reactor.core.publisher.Mono; import org.springframework.core.convert.converter.Converter; @Configuration @EnableWebFluxSecurity public class SecurityConfig { @Bean SecurityWebFilterChain filterChain(ServerHttpSecurity httpSecurity) throws Exception { httpSecurity.authorizeExchange((exchanges) -> exchanges .pathMatchers(HttpMethod.POST, "/customer").hasRole("admin") .pathMatchers("/account/**").authenticated() .pathMatchers("/customer/**").authenticated()) .oauth2ResourceServer(oAuth2 -> oAuth2 .jwt(jwtSpec -> jwtSpec.jwtAuthenticationConverter(grantedAuthoritiesExtractor()))); httpSecurity.csrf(ServerHttpSecurity.CsrfSpec::disable); return httpSecurity.build(); } private Converter<Jwt, Mono<AbstractAuthenticationToken>> grantedAuthoritiesExtractor() { JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new AuthServerRoleConverter()); return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter); } }
Note that as per configuration, only the user having the role as admin can access POST request going to /customer. For other requests , only authentication is required there is no authorization.
oauth2ResourceServer() method verifies an access token before forwarding request to services kept behind the API gateway. Since
we have also configured some roles for the users so that information is also passed to the resource server in the form of
SimpleGrantedAuthority
objects.
import org.springframework.core.convert.converter.Converter; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.jwt.Jwt; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class AuthServerRoleConverter implements Converter<Jwt, Collection<GrantedAuthority>>{ @Override public Collection<GrantedAuthority> convert(Jwt jwt) { Map<String, Object> realmAccess = jwt.getClaim("realm_access"); if (realmAccess == null || realmAccess.isEmpty()) { return new ArrayList<>(); } List<String> roles = (List<String>) realmAccess.get("roles"); Collection<GrantedAuthority> grantedAuthorities = roles.stream() .map(roleName -> "ROLE_" + roleName) .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); //System.out.println(grantedAuthorities); return grantedAuthorities; } }
Test using Postman
Create a GET request for URL- localhost:8181/customer/2 (Please make the request as per the ID you have, I have a Customer with ID 2 in DB).
Try sending the request to backend without setting any type of authorization configuration. You should get "401 Unauthorized" as response.
Authorization settings in Postman
Select "Authorization" tab in Postman and below that select type as Oauth 2.0 and "Add authorization data to" as Request Header.
In the "Configure new token" section provide the following configuration options.
Token Name- accesstoken
Grant Type- Select Authorization code
Callback URL- Check Authorize using browser (So that login page is opened in a browser)
To get values for these two properties "Auth URL" and "Access Token URL " go to "Realm Settings" and all the way down in the displayed screen. There click on OpenID Endpoint Configuration
Select value of authorization_endpoint and enter it in Auth URL, select value of token_endpoint and enter it in Access Token URL
Client ID- Name selected for the client (customer-client). Get it from Keycloak admin console.
Client Secret- Client secret displayed for the client. Get it from Keycloak admin console.
Scope- openid email profile (scope for access is email and profile of the user)
State- ab12-cd34-ef56 (Give an alphanumeric value that is used for preventing cross-site request forgery.
Client Authentication- select "send client credentials in body"
Then click "Get New Access Token"
Ensure that you are not already logged in to the Keycloak server in your default browser as admin, otherwise it will show you already logged in as admin. What we need is to log in as either test_user or test_admin.
Once you click on "Get New Access Token" in Postman it should open the Keycloak login page. Lets login with test_user and its password.
Once you enter the credentials and click sign in browser should redirect you to Postman with the access token. If that doesn't happen check that browser is allowing popups for this URL.
Click on use token so that the sent token is used as the bearer token with the request. Click Send to send the GET request to the backend. This time you should get the Customer details.
POST mapping
Create a Post request for URL- localhost:8181/customer with body as
{ "name": "Suresh", "age": 35, "city": "Bangalore" }
Authorization setting remains same and user logged in is "test_user"
Click on use token so that the sent token is used as the bearer token with the request. Click Send to send the Post request to the backend.
Since "user" role doesn't have authority to make a POST request so 403 Forbidden is sent as response.
Click again on "Get New Access Token" and login as "test_admin" user. Now POST request should work.
That's all for this topic Spring Boot Microservice + API Gateway + OAuth2 + Keycloak. If you have any doubt or any suggestions to make please drop a comment. Thanks!
>>>Return to Spring Tutorial Page
Related Topics
You may also like-
No comments:
Post a Comment