Zum Inhalt

Keycloak per Custom-API erweitern

Ziel

In dieser Aufgabe erweitern Sie Keycloak, indem Sie eine eigene REST API in Java entwickeln und als Extension in Keycloak einbinden. Sie legen ein Java-Projekt an, kompilieren eine JAR, integrieren diese in ein Docker-Image und starten einen Keycloak-Container mit Ihrer Erweiterung. So lernen Sie, wie Sie Keycloak an individuelle Anforderungen anpassen können.

Hilfsmittel

  • Versuchen Sie, die unten stehenden Aufgaben mit Hilfe der Folien eigenständig zu lösen.
  • Sollten Sie dabei Probleme haben, finden Sie bei jeder Aufgabe einen ausklappbaren Block, in dem der Lösungsweg beschrieben wird.

Aufgabe 1 - Custom REST API für Nutzer

Zunächst wollen wir eine API für Nutzer erstellen. Dafür müssen wir:

  1. ein Java-Projekt anlegen
  2. eine JAR kompilieren
  3. Keycloak mit dem zusätzlichen Provider starten

1.1 Java-Projekt anlegen

  • Führen Sie folgende Befehle im Terminal aus. Dadurch wird die notwendige Ordner-Struktur erzeugt.
cd /home/coder/workspace
mkdir -p custom-spi/src/main/java/de/corewire/keycloak/restapi
touch custom-spi/src/main/java/de/corewire/keycloak/restapi/CustomEndpointProviderFactory.java
touch custom-spi/src/main/java/de/corewire/keycloak/restapi/CustomEndpointProvider.java
mkdir -p custom-spi/src/main/resources/META-INF/services
touch custom-spi/src/main/resources/META-INF/services/org.keycloak.services.resource.RealmResourceProviderFactory
touch custom-spi/pom.xml
touch custom-spi/Dockerfile
  • Kopieren Sie den Inhalt der pom.xml und machen Sie sich mit dem Inhalt vertraut.
  • Stellen Sie sicher, dass die aktuelle Keycloak-Version verwendet wird.
Inhalt 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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>de.corewire.keycloak</groupId>
    <artifactId>restapi</artifactId>
    <version>0.0.1</version>

    <properties>
        <keycloak.version>26.1.3</keycloak.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-server-spi</artifactId>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-server-spi-private</artifactId>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-services</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.keycloak</groupId>
                <artifactId>keycloak-parent</artifactId>
                <version>${keycloak.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <finalName>${project.groupId}-${project.artifactId}-${project.version}</finalName>
    </build>

</project>

1.2 ProviderFactory anlegen

  • Bearbeiten Sie die Datei CustomEndpointProviderFactory.java. Aktuell ist sie noch leer. Sie soll:
    • Die Klasse CustomEndpointProviderFactory enthalten
    • Das Interface RealmResourceProviderFactory implmentieren
    • Die create-Funktion soll eine CustomEndpointProvider-Instanz erstellen und zurückgeben.
Tipp (Klicken Sie auf den Pfeil, falls Sie nicht weiterkommen)
package de.corewire.keycloak.restapi;

import org.keycloak.Config.Scope;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.services.resource.RealmResourceProvider;
import org.keycloak.services.resource.RealmResourceProviderFactory;

public class CustomEndpointProviderFactory implements RealmResourceProviderFactory {
}
Lösung (Klicken Sie auf den Pfeil, falls Sie nicht weiterkommen)
package de.corewire.keycloak.restapi;

import org.keycloak.Config.Scope;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.services.resource.RealmResourceProvider;
import org.keycloak.services.resource.RealmResourceProviderFactory;

public class CustomEndpointProviderFactory implements RealmResourceProviderFactory {

    @Override
    public RealmResourceProvider create(KeycloakSession keycloakSession) {
        return new CustomEndpointProvider(keycloakSession);
    }

    @Override
    public void init(Scope scope) {
    }

    @Override
    public void postInit(KeycloakSessionFactory keycloakSessionFactory) {
    }

    @Override
    public void close() {
    }

    @Override
    public String getId() {
        return "custom-endpoint";
    }
}

1.3 Provider anlegen

  • Bearbeiten Sie die Datei CustomEndpointProvider.java. Aktuell ist sie noch leer. Sie soll:
    • Die Klasse CustomEndpointProvider enthalten
    • Das Interface RealmResourceProvider implmentieren
    • Einen Konstruktor enthalten, der einen KeycloakSession als Parameter entgegen nimmt und in einer Instanz-Variable speichert.
    • Einen simplen Endpunkt definieren
      @GET
      @Path("hello")
      public Response hello() {
          return Response.ok(Map.of("hello", "world")).build();
      }
      
Tipp (Klicken Sie auf den Pfeil, falls Sie nicht weiterkommen)
package de.corewire.keycloak.restapi;

import java.util.Map;

import org.keycloak.models.KeycloakSession;
import org.keycloak.services.resource.RealmResourceProvider;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Response;

public class CustomEndpointProvider implements RealmResourceProvider {
}
Lösung (Klicken Sie auf den Pfeil, falls Sie nicht weiterkommen)
package de.corewire.keycloak.restapi;

import java.util.Map;

import org.keycloak.models.KeycloakSession;
import org.keycloak.services.resource.RealmResourceProvider;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Response;

public class CustomEndpointProvider implements RealmResourceProvider {
    private final KeycloakSession session;

    public CustomEndpointProvider(KeycloakSession session) {
        this.session = session;
    }

    @Override
    public Object getResource() {
        return this;
    }

    @Override
    public void close() {
    }

        @GET
    @Path("hello")
    public Response hello() {
        return Response.ok(Map.of("hello", "world")).build();
    }
}

1.4 ProviderFactory registrieren

  • Als letztes müssen wir die CustomEndpointProviderFactory noch als RealmResourceProviderFactory registrieren.
  • Tragen Sie dafür de.corewire.keycloak.restapi.CustomEndpointProviderFactory in die Datei org.keycloak.services.resource.RealmResourceProviderFactory ein.

1.5 Compilieren und Keycloak erweitern

  • Kopieren Sie den Inhalt vom Dockerfile aus den Folien in die leere Dockerfile-Datei.
  • Passen Sie das Dockerfile entsprechend an
Lösung (Klicken Sie auf den Pfeil, falls Sie nicht weiterkommen)
  • Da wir für den Test keine externe Datenbank nutzen, können alle ENV-Variablen gelöscht werden, die die Datenbank konfigurieren
    FROM maven:3.9.9-amazoncorretto-23 as builder
    WORKDIR /user/src/mymaven
    
    COPY pom.xml /user/src/mymaven
    RUN mvn dependency:resolve
    COPY src /user/src/mymaven/src
    RUN mvn package
    
    FROM quay.io/keycloak/keycloak:latest
    COPY --from=builder --chown=keycloak:root \
        /user/src/mymaven/target/*.jar \
        /opt/keycloak/providers
    
    RUN /opt/keycloak/bin/kc.sh build
    
  • Bauen Sie das Image
Lösung (Klicken Sie auf den Pfeil, falls Sie nicht weiterkommen)
cd /home/coder/workspace/custom-spi
docker build -t kc-custom-spi .
  • Starten Sie das Image mit
    docker run --rm --network=proxy --label-file ../labels.txt -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin -e KC_PROXY_HEADERS=xforwarded kc-custom-spi start-dev --hostname=https://keycloak.code-0.labs.corewire.de
    

1.6 Custom REST API testen

  • Testen Sie, ob die API funktioniert.
  • Wurde der Provider geladen?
Lösung (Klicken Sie auf den Pfeil, falls Sie nicht weiterkommen)

Finden Sie den Eintrag custom-endpoint in Provider Info?

  • Besuchen Sie die URL https://keycloak.code-{ZAHL}.labs.corewire.de/realms/master/custom-endpoint/hello
  • Verändern Sie den Endpunkt nach belieben

(Optional) Aufgabe 2 - Custom REST API für Admins

Falls Sie noch Zeit übrig haben, können Sie sich optional an folgender Aufgabe versuchen:

2.1 Custom Admin-API anlegen

  • Erstellen Sie einen Admin-Endpunkt:
    • Erstellen Sie eine Klasse CustomAdminEndpointProviderFactory, die AdminRealmResourceProviderFactory implementiert
    • Erstellen Sie eine Klasse CustomAdminEndpointProvider, die AdminRealmResourceProvider impementiert
    • Registrieren sie die ProviderFactory

2.2 Custom Admin-API testen

  • Die Admin API ist nicht dirkt über den Browser erreichbar.
  • Schauen Sie, ob der Provider in der Provider-Info aufgelistet ist.
  • Testen Sie ihre API mit:
# Access Token holen:
TOKEN=$(curl -X POST "https://keycloak.code-{ZAHL}.labs.corewire.de/realms/master/protocol/openid-connect/token" -H "Content-Type: application/x-www-form-urlencoded" -d "client_id=admin-cli" -d "username=<user>" -d "password=<pass>" -d "grant_type=password" | jq -r ".access_token")

# Api testen:
curl -X GET "https://keycloak.code-{ZAHL}.labs.corewire.de/admin/realms/master/<provider-id>/<endpoint>" -H "Authorization: Bearer $TOKEN"