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


Mother's Friend's Son
Mother’s Friend’s Son

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:

  1. Create a new realm for your project

  1. Create a new client for your new realm

  1. Create some test users and assign some test roles to them. In my case, these are users:

    1. username: arnur, roles: [“USER”]

    2. username: adal, roles: [“USER”]

  2. 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: "название вашего реалма"
  1. 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!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *