Custom SPIs
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:
- ein Java-Projekt anlegen
 - eine JAR kompilieren
 - 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 
CustomEndpointProviderFactoryenthalten - Das Interface 
RealmResourceProviderFactoryimplmentieren - Die 
create-Funktion soll eineCustomEndpointProvider-Instanz erstellen und zurückgeben. 
 - Die Klasse 
 
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 
CustomEndpointProviderenthalten - Das Interface 
RealmResourceProviderimplmentieren - 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(); } 
 - Die Klasse 
 
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 
CustomEndpointProviderFactorynoch alsRealmResourceProviderFactoryregistrieren. - Tragen Sie dafür 
de.corewire.keycloak.restapi.CustomEndpointProviderFactoryin die Dateiorg.keycloak.services.resource.RealmResourceProviderFactoryein. 
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-{ZAHL}.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, dieAdminRealmResourceProviderFactoryimplementiert - Erstellen Sie eine Klasse 
CustomAdminEndpointProvider, dieAdminRealmResourceProviderimpementiert - Registrieren sie die ProviderFactory
 
 - Erstellen Sie eine Klasse 
 
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"