KeyCloak and micro-services. How to make life easier for a programmer

Do you want to make your life easier and save time?
Hello! If you, like me, decide to use keycloak for authentication and authorization in my micro-service architecture, then I will tell you how to configure it yourself keycloak, its working environment and at the end we will connect Active Directory to our application. Before reading this guide, please read keycloak at this link: https://habr.com/ru/company/southbridge/blog/654475/
Stack Technology
I Javist from skin to bones, and that is why all our micro-services will be written in conjunction with Java 18 and Spring Boot. I have a Maven project collection, as it is quite simple in its application.
Information on other services:
PostGreSql – database
PgAdmin – DB GUI
Authentication Service – KeyCloak V19
Zipkin + Sleuth – Request Tracing Technology
Kafka – Message Broker
Eureka Server – registering our micro-services
Ready docker-compose file:
services:
postgres:
container_name: postgres-gilgamesh
image: postgres
environment:
POSTGRES_USER: "здесь ваш username"
POSTGRES_PASSWORD: "здесь ваш password"
POSTGRES_HOST_AUTH_METHOD: trust
POSTGRES_DB: keycloak_db
volumes:
- postgres:/data/postgres
ports:
- "5432:5432"
networks:
- postgres
restart: unless-stopped
keycloak:
image: quay.io/keycloak/keycloak:legacy
platform: linux/arm64
environment:
DB_VENDOR: POSTGRES
DB_ADDR: postgres
DB_SCHEMA: public
DB_DATABASE: keycloak_db
DB_USER: "здесь ваш username от БД"
DB_PASSWORD: "здесь ваш password от БД"
KEYCLOAK_USER: "здесь ваш username"
KEYCLOAK_HOSTNAME: localhost
KEYCLOAK_PASSWORD: "здесь ваш пароль"
ports:
- 8082:8080
depends_on:
- postgres
networks:
- postgres
#Образ Готового LDAP если у вас нет тестового варианта
ldap:
image: rroemhild/test-openldap
ports:
- 10389:10389
- 10636:10636
networks:
- postgres
pgadmin:
container_name: pgadmin-gilgamesh
image: dpage/pgadmin4
environment:
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-pgadmin4@pgadmin.org}
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-admin}
PGADMIN_CONFIG_SERVER_MODE: 'False'
volumes:
- pgadmin:/var/lib/pgadmin
ports:
- "5050:80"
networks:
- postgres
restart: unless-stopped
zipkin:
image: openzipkin/zipkin
container_name: zipkin-gilgamesh
ports:
- "9411:9411"
networks:
- spring
zookeeper:
image: confluentinc/cp-zookeeper:latest
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
ports:
- 22181:2181
kafka:
image: confluentinc/cp-kafka:latest
depends_on:
- zookeeper
ports:
- 29092:29092
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
networks:
postgres:
driver: bridge
spring:
driver: bridge
volumes:
postgres:
pgadmin:
Setting up and configuring our parent pom.xml
This project will run locally and all micro-services will be in the same directory. Thanks to this, we can use the maven feature in which we can specify the parent file, where our main dependencies will lie, which we will not need to register several times.
There will be several modules in my project, let’s immediately specify them in our parent pom.xml file:
<modules>
<module>eureka-server</module>
<module>incidents</module>
<module>apigw</module>
<module>clients</module>
<module>kafka</module>
<module>authentication</module>
</modules>
Your modules may differ from mine, as you may have a completely different architecture with different services.
To manage third-party dependencies, maven has a special dependencyManagement section and an inheritance mechanism.
Thanks to dependencyManagement in maven, we can add parent dependencies to our project, thanks to which we can use all the functionality of its heirs. It looks something like this:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2.6.7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.6.7</version>
<scope>import</scope>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.keycloak.bom</groupId>
<artifactId>keycloak-adapter-bom</artifactId>
<version>20.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
“Advanced” Automatic wrapping of service in docker
For automatic image wrapping in docker, I use my favorite JIB.
Jib creates optimized Docker and OCI images for your Java applications without using the Docker daemon – and without deep learning Docker best practices. It is available as plugins for Maven and Gradle, and as a Java library.
Main objectives of the JIB
Rapidity – Rapid deployment of changes. Jib separates your application into several layers, separating dependencies from classes. Now you don’t have to wait for Docker to rebuild your entire Java application – you only need to deploy the layers that have changed.
Reproducibility – Rebuilding a container image with the same content always creates the same image. Never call unnecessary updates again.
No daemon – Reduce CLI dependencies. Build your Docker image from Maven or Gradle and push it to any registry of your choice. No more writing Docker files and calling docker build/push.
You can read more about JIB here: https://habr.com/en/post/552494/. In my case, the configuration looks like this:
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.6.7</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<from>
<image>eclipse-temurin:17</image>
<platforms>
<platform>
<architecture>arm64</architecture>
<os>linux</os>
</platform>
<platform>
<architecture>amd64</architecture>
<os>linux</os>
</platform>
</platforms>
</from>
<to>
<tags>
<tag>latest</tag>
</tags>
</to>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>
build
</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
Setting up a simple service
Let’s create a simple service called Incident and add a few routes to it and secure them with keycloak.
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>gilgamesh_project</artifactId>
<groupId>com.project</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>incidents</artifactId>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>build-docker-image</id>
<build>
<plugins>
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.project</groupId>
<artifactId>kafka</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
application.yml file:
server:
port: 8080
spring:
application:
name: incidents
datasource:
password: password
url: jdbc:postgresql://localhost:5432/incident-service
username: postgres
jpa:
hibernate:
ddl-auto: update
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect
format_sql: true
show-sql: true
zipkin:
base-url: http://localhost:9411
kafka:
bootstrap-servers: localhost:29092
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
fetch-registry: true
register-with-eureka: true
For the sake of brevity, let’s assume that we have already connected a message broker and we have a service that calls another service to get information about some entity. Next, we paint our test controller:
@RestController
@RequestMapping("api/v1/incident/service")
@RequiredArgsConstructor
public class IncidentController {
private final IncidentService incidentService;
@PostMapping("/create")
public ResponseEntity<IncidentCreateDtoResponse> create(@Valid @RequestBody IncidentDtoRequest incidentDtoRequest) {
IncidentCreateDtoResponse incidentDtoResponse = IncidentCreateMapper.incidentCreateToDto(incidentService.create(incidentDtoRequest),new ArrayList<>());
return new ResponseEntity<>(incidentDtoResponse, HttpStatus.OK);
}
}
Great, we have set up our service, now let’s connect keycloak to it.
Setting up and running KeyCloak
Open the url that you specified in the keycloak variables in docker-compose. You should be greeted with the following picture:

If you see this window, then congratulations, you have successfully launched keycloak. Now click on the admin console and log into the system using the data that you entered in the docker compose file.
Then do everything according to this algorithm of actions:
Create a new realm for your project

Create a new client for your new realm

Create some test users and assign some test roles to them. In my case, these are users:
username: arnur, roles: [“USER”]
username: adal, roles: [“USER”]
Now add your keycloak’s credentials to your application.yml so that later on our Spring Boot application can generate tokens and authorize the user:
keycloak:
auth-server-url: http://localhost:"ваш порт"/auth
resource: "название вашего клиента"
bearer-only: true
public-client: true
realm: "название вашего реалма"
Now it’s time to set up your spring security. To do this, when creating your configuration, you will inherit from the class KeycloakWebSecurityConfigurerAdapter. This class allows us to create sessions and configure the security of our routes. Code example:
@KeycloakConfiguration
@Import(KeycloakSpringBootConfigResolver.class)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
/**
* Registers the KeycloakAuthenticationProvider with the authentication manager.
*/
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = new KeycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
/**
* Defines the session authentication strategy.
*/
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(buildSessionRegistry());
}
@Bean
protected SessionRegistry buildSessionRegistry() {
return new SessionRegistryImpl();
}
@Override
protected void configure(HttpSecurity http) throws Exception
{
super.configure(http);
http.cors().and().csrf().disable();
http.authorizeRequests()
.antMatchers("/api/v1/incident/service/**").hasRole("USER");
http.authorizeRequests().anyRequest().permitAll();
}
}
Congratulations, now you can authenticate with oauth2.0 token via keycloak. To check this use postman and select authorization via token. Here is the default setting:
Connecting LDAP to your project
Now the juice begins! KeyCloak allows us to use users from AD for authorization. For example, I will use a ready-made LDAP image, which you can download from this link: https://hub.docker.com/r/rroemhild/test-openldap/. Make a pool and deploy in docker, we live in the 21st century!
To add LDAP to your Keycloak, go to the “User Federation” section and select LDAP as your “User Provider” and specify everything according to the instructions:
Console display name: to your heart’s content
Connection URL: here if you deployed ldap via docker url will be like this: ldap://”ip of your computer”:10389. For some reason, keycloak completely refuses to see your ldap through localhost even if you inject them into the same docker-network
Bind Type: specifies the BN of your admin in case of test LDAP it is: cn=admin,dc=planetexpress,dc=com
Bind credentials: specifies your admin password in case of test LDAP it is: GoodNewsEveryone
EDIT MODE: I advise you to put READ_ONLY
User DN: indicates the general information of your ordinary user in case of test LDAP it is: ou=people,dc=planetexpress,dc=com
Username LDAP attribute: The name of the LDAP attribute that is displayed as the Keycloak username. For many LDAP server vendors, this may be ‘uid’. For Active directory this can be ‘sAMAccountName’ or ‘cn’. The attribute must be filled in for all LDAP user entries that you want to import from LDAP into Keycloak. For our test LDAP, this is: uid
RDN LDAP attribute: The name of the LDAP attribute that is used as the RDN (top attribute) of the user’s typical DN. This is usually the same as the LDAP Username attribute, but this is not required. For example, it is common for Active directory to use ‘cn’ as the RDN attribute, when the username attribute might be ‘sAMAccountName’. For our test LDAP, this is: uid
UUID LDAP attribute: The name of an LDAP attribute that is used as the unique object identifier (UUID) for objects in LDAP. For many LDAP server vendors, this is ‘entryUUID’; however, some vendors use other options. For example, for Active directory it should be ‘objectGUID’. If your LDAP server does not support the concept of a UUID, you can use any other attribute that must be unique among the LDAP users in the tree. For example, ‘uid’ or ‘entryDN’. For our test LDAP this is: entryUUID
User object classes: All values of the LDAP objectClass attribute for users in LDAP, separated by commas. For example: ‘inetOrgPerson, organizationalPerson’. Newly created Keycloak users will be written to LDAP with all of these object classes, and existing LDAP user records will only be found if they contain all of these object classes. For our test LDAP, these are: inetOrgPerson, organizationalPerson, person, top
After that press save and in action choose sync all users. After that, all users from ldap should be synchronized with your keycloak client.
Then go to the Users tab and you should see something like this:

Now try to give some users the role you created earlier (in my case it is USER). And try to generate a token. If everything worked out for you, then congratulations, you have successfully configured KeyCloak as an authentication service.
RESULTS
Keycloak is a super versatile authorization and authentication service. It greatly saves development time and allows you to customize the authorization system as you like. Keycloak is also extremely easy to configure and run on any system, and is also open source. And most importantly, it is written in Java!
All good and positive! Use Kcell and Activ!