Most projects that use Keycloak make use of some kind of configuration automation. While you can literally do almost anything via the admin UI of Keycloak, it also supports other ways of configuration, such as:
- JSON file imports
- Shell script configuration via the Keycloak admin CLI
- Configuration as code (i.e. the Keycloak admin REST client in Java)
When using Keycloak in bigger (enterprise) projects, you will want to use a more sophisticated way of configuring your instance than just “clicking things together” in the admin interface.
In the following few blog posts, and as part of the “Configuration as code with Keycloak” series, we will create a brand-new project, that will eventually result in a completely containerized Keycloak instance that is entirely configured by Java code.
In detail, our acceptance criteria for this demo project include the following:
- The output of our build is one docker image that includes Keycloak, our custom Keycloak extensions code and the configuration code
- The latest version of Keycloak is used (>=20.0.3 at time of writing)
- All configuration of our Keycloak instance has to be achieved in form of code. If we clear the underlying database, the only thing we need to do is re-start our Keycloak instance and re-run the configuration script.
- Keycloak can be made environment aware (i.e. DEV, ACC, PRD) via configuration properties
The Why
The first question we should ask ourselves is why go through the hassle of writing (potentially) a lot of Java code if there exists the possibility of using the admin UI of Keycloak to configure the things one needs and subsequently exporting them into a JSON file we can simply auto-import later on?
Simply put, there are the following advantages:
- Our configuration becomes unit testable.
- Version control on Java classes is easier to manage than on 15k+ lines of code JSON files.
- The team is in control of the code it writes. The export function to a JSON file by Keycloak we do not, and we have no influence on the order of export.
- Configuration code becomes readable and easier to work with.
- We have fine-grained control over dependencies used in our Keycloak container image and as such have more flexibility when it comes to CVE patches.
In the first blog post of this series, the knowledge base was created. We learnt what Keycloak is capable of in general terms and what basic building blocks are available in Keycloak. We will now start to create our base project setup using Maven.
The Maven Project Setup
In this section we will create the base directory structure and initialize the maven project.
The directory structure
Create your root directory with and switch the terminal location into it by running
mkdir keycloak-configAsCode-demo && cd keycloak-configAsCode-demo
We now create two maven artifacts called keycloak and java-configuration by running the following two commands in our terminal window:
mvn archetype:generate -DgroupId=nl.the_experts.keycloak -DartifactId=java-configuration -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 -DinteractiveMode=false
and
mvn archetype:generate -DgroupId=nl.the_experts.keycloak -DartifactId=keycloak -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 -DinteractiveMode=false
Cleanup of generated files
Remove the generated files java-configuration/src/main/java/nl/the_experts/keycloak/App.java
and keycloak/src/main/java/nl/the_experts/keycloak/App.java
and the corresponding test files.
Create the parent pom.xml file
In the root directory of the project create a file named pom.xml
with the following content:
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>nl.the_experts.keycloak</groupId>
<artifactId>keycloak-configascode-demo</artifactId>
<version>0.0.1-local</version>
<packaging>pom</packaging>
<modules>
<module>keycloak</module>
<module>java-configuration</module>
</modules>
<properties>
<!-- Java -->
<java.version>17</java.version>
<!-- Maven -->
<maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version>
<maven.shade.plugin.version>3.3.0</maven.shade.plugin.version>
<maven.jar.plugin.version>3.2.2</maven.jar.plugin.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<maven.build.timestamp.format>yyyy-MM-dd'T'HH:mm:ss'Z'</maven.build.timestamp.format>
<!-- Project specific -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- Keycloak -->
<keycloak.version>20.0.3</keycloak.version>
<quarkus.version>2.13.3.Final</quarkus.version>
<quarkus.resteasy.version>2.13.5.Final</quarkus.resteasy.version>
<quarkus.native.builder-image>mutable-jar</quarkus.native.builder-image>
<!-- Plugins -->
<build-helper-maven-plugin.version>3.3.0</build-helper-maven-plugin.version>
</properties>
<dependencyManagement>
<dependencies>
<!--Quarkus-->
<dependency>
<groupId>io.quarkus.arc</groupId>
<artifactId>arc</artifactId>
<version>${quarkus.version}</version>
</dependency>
<!--keycloak-->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-quarkus-server</artifactId>
<version>${keycloak.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-quarkus-dist</artifactId>
<version>${keycloak.version}</version>
<type>zip</type>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-client</artifactId>
<version>${keycloak.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<version>${keycloak.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId>
<version>${keycloak.version}</version>
</dependency>
<!--REST-->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jackson</artifactId>
<version>${quarkus.resteasy.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client</artifactId>
<version>${quarkus.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client-jackson</artifactId>
<version>${quarkus.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<excludes>
<exclude>**/*IntegrationTest.java</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>unpack-keycloak-server-distribution</id>
<phase>package</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-quarkus-dist</artifactId>
<type>zip</type>
<outputDirectory>target</outputDirectory>
</artifactItem>
</artifactItems>
<excludes>**/lib/**</excludes>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.version}</version>
<configuration>
<finalName>keycloak</finalName>
<buildDir>${project.build.directory}/keycloak-${keycloak.version}</buildDir>
</configuration>
<executions>
<execution>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
Let's briefly analyze the different sections we have now added to our root parent POM. In preparation for our base Keycloak setup the most basic blocks are already in there. We will use those in Pt.2 of this blog series.
- In the
modules
element we register our two submodules we previously created. - In the
properties
element we maintain all the (version) variables that we will use throughout our project. - In the
dependencyManagement
element, all dependencies and their versions we want to use should be added. - Contained in the
build
section you will find thepluginManagement
where we define the plugins that we will use later on throughout our project.
This is not a Maven tutorial though, so if you want to learn more about how these elements work, you can find all the information here. Let us continue with our setup so we can actually write some code for Keycloak.
Updating the child pom.xml files
All that remains is to update the child POMs and we are ready to go. Please replace the content of ./keycloak/pom.xml
with the following snippet:
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>nl.the_experts.keycloak</groupId>
<artifactId>keycloak-configascode-demo</artifactId>
<version>0.0.1-local</version>
</parent>
<artifactId>keycloak</artifactId>
</project>
and accordingly, ./java-configuration/pom.xml
with this snippet:
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>nl.the_experts.keycloak</groupId>
<artifactId>keycloak-configascode-demo</artifactId>
<version>0.0.1-local</version>
</parent>
<artifactId>java-configuration</artifactId>
</project>
After having completed the above steps, your project tree should look something like this:
Package our maven project
You should now be able to package the parent maven project by running the following command from the project root directory:
mvn clean package
Your result should look something like this:
For now, our recently created project and its submodules are completely empty. We have prepared some build steps in our pluginManagement but we are not using that yet. In the upcoming blog Keycloak - Configuration as Code Pt.2, we will continue with our project by setting up a clean and basic Keycloak distribution that we can run within a container platform such as Docker.
Stay tuned for more.
The code from this tutorial step can be found here.
Discussion (0)