1. Introduction
This article aims to demonstrate how multi-tenancy can be achieved in a microservice using Keycloak and Spring with Spring API Gateway in front of the services.
Spring API Gateway brings an out-of-the-box flexible routing with security, resilience, and monitoring management. How does Keycloak validate realm information and forward it to microservices?
In the example microservice project, the tenant configuration will be loaded based on the Keycloak-Realm header information to authenticate and authorize a user correctly.
2. The Problem
In every software application, there is a need to create and manage users. We will design a software architecture where the client request is handled by an API Gateway and routed to the user service.
Use Case Diagram.
Sequence Diagram
3. Keycloak
Keycloak is an open-source identity and access management platform. It provides out-of-the-box features such as authentication and authorization.
For this exercise, we will use a custom Keycloak server from https://github.com/czetsuyatech/ct-keycloak-iam. This project downloads a specific version of the Keycloak server and builds customizations such as theme, provider, realm, and more.
This exercise needs two tenants to demonstrate multi-tenancy, and thus the need to create two Keycloak realms.
4. Microservice Project in Spring
Create a Spring service project and name it user-services. It will have one controller, UserController, and one endpoint that returns the information of the current log user, depending on which tenant the user belongs to.
In the getUserInfo method below, we are getting the Keycloak token from the security context and getting the principal. We can then typecast the object into KeycloakPrincipal, where we can get the accessToken and the user's primary attributes, such as first and last name.
@GetMapping("/user-info")
public String userInfo() {
return getUserInfo();
}
@SuppressWarnings("unchecked")
private String getUserInfo() {
KeycloakAuthenticationToken authentication = (KeycloakAuthenticationToken) SecurityContextHolder.getContext()
.getAuthentication();
final Principal principal = (Principal) authentication.getPrincipal();
String tokenInfo = null;
if (principal instanceof KeycloakPrincipal) {
KeycloakPrincipal<keycloaksecuritycontext> kPrincipal = (KeycloakPrincipal<keycloaksecuritycontext>) principal;
KeycloakSecurityContext ksc = kPrincipal.getKeycloakSecurityContext();
IDToken token = ksc.getIdToken();
AccessToken accessToken = kPrincipal.getKeycloakSecurityContext().getToken();
tokenInfo = accessToken.getSubject();
// this value is the one use to call another service as bearer token
// Authorization : Bearer kcs.getTokenString()
// use this link to read the token https://jwt.io
return String.format("Hello %s %s [subject=%s]", accessToken.getGivenName(), accessToken.getFamilyName(),
tokenInfo);
}
return "Hello World";
}
- Spring API Gateway Project
We need another Spring project that will have a dependency on the spring-cloud-starter-gateway. In this project, we must define request routing and Jwt issuer URIs for each realm.
With TokenRelayGatewayFilterFactory, it acts as an OAuth2 consumer and forwards the incoming token to the outgoing request resource, in this case to the user service.
@Bean
public RouteLocator gatewayRouter(RouteLocatorBuilder builder, TokenRelayGatewayFilterFactory filterFactory) {
return builder.routes()
.route(p -> p.path("/test")
.uri("http://httpbin.org"))
.route("users", p -> p.path("/users/**")
.filters(f -> f.filter(filterFactory.apply()))
.uri("http://localhost:8001"))
.build();
}
- Testing
6.1 Keycloak
You can use the custom Keycloak project I linked in the references section. You will need two realms to test the application. Included in the project.
6.2 Postman Collection
I am attaching a Postman collection that you can use to test the endpoints. Included in the project.
Tenant1 / User1
Tenant2 / User2
6.3 User Service
6.4 API Gateway
7. Improvements
To avoid redeploying a war file, add a new tenant keycloak.json file and external storage such as S3.
Top comments (0)