Die Identity- und Access-Management-Lösung Keycloak erfreut sich in den letzten Jahren weiter Verbreitung. Trotz vieler Funktionen sind in der Praxis häufig individuelle Anpassungen erforderlich. Wie kann man solche Erweiterung vornehmen – von der Bereitstellung eines eigenen Moduls, über die Registrierung eines Providers bis hin zu Aspekten wie Logging und Konfiguration?

Keycloak ist eine Open-Source Identity- und Accessmanagement-Lösung. Für individuelle Erweiterungen stehen in Keycloak eine Reihe von Erweiterungspunkten, sogenannte Service-Provider-Interfaces (SPI) zur Verfügung, um vorhandene Funktionalität auszutauschen oder an die eigenen Bedürfnisse anzupassen.

Übersicht der Keycloak-Provider auf der Server-Info-Page. (Abb. 1)

Keycloak bietet eine ganze Reihe an SPIs, die als Erweiterungs- bzw. Anpassungspunkte dienen können. Da die Dokumentation längst nicht alle vorhandenen SPIs auflistet, empfiehlt sich für eine Gesamtübersicht ein Blick auf Keycloaks Server-Info-Page (Abb. 1). Hierzu reicht es aus, eine Keycloak-Instanz mittels Docker zu starten (Listing 1).

(Listing 1)

Anschließend steht die Server-Info-Page mit einer Liste aller SPIs und verfügbarer Implementierung in der Administrationskonsole zur Verfügung. Der Login erfolgt mit Benutzername test und Passwort test.

Im Rahmen dieses Artikels soll die SPI emailSender als Beispiel dienen. Wie die Server-Info-Page zeigt, gibt es dazu in Keycloak genau eine Implementierung bzw. einen Provider namens default. Die Implementierung dient zum Versand von E-Mails und nutzt die SMTP-Einstellungen, die in der Konsole unter Realm Settings im Untermenü Email konfiguriert werden können. Als exemplarische Anforderung für eine Erweiterung soll es möglich sein, die Betreffzeilen aller E-Mails mit einem konfigurierbaren Präfix zu versehen. Beispielsweise soll ein Präfix KEYCLOAK: konfiguriert werden können, so dass eine E-Mail mit dem eigentlichen Betreff Ihr Passwort wurde geändert nun den Betreff KEYCLOAK: Ihr Passwort wurde geändert trägt.

Ein Grundgerüst für die Implementierung

Die Implementierung einer SPI wird in der Regel als JAR-Datei ausgeliefert und später deployt. Zur Generierung eines JARs kann ein einfaches Maven-Projekt genutzt werden. Folgende Maven-Dependencies sind für eine Implementierung der SPI notwendig und dem Projekt hinzuzufügen (Listing 2).

(Listing 2)

Die SPI emailSender ist durch das Interface EmailSenderProvider definiert. Die eigene Implementierung erfolgt einfach über die Implementierung dieses Interfaces wie in (Listing 3) dargestellt.

(Listing 3)

Zusätzlich benötigt diese Implementierung eine zugehörige Factory im Sinne des Factory-Patterns. Die Implementierung erfolgt über das entsprechende Interface EmailSenderProviderFactory (Listing 4).

(Listing 4)

Dabei wird in der Methode create eine Instanz des Providers erzeugt und in der Methode getId ein Name für die SPI-Implementierung definiert. Dieser Name ist äquivalent zum Namen default, der für die Standardimplementierung auf der Server-Info-Page zu sehen war.

Damit ist das Grundgerüst für eine Implementierung prinzipiell bereits erstellt. Es fehlt lediglich eine Datei, um den Provider zu registrieren, wie sie für Java-Service-Provider-Interfaces üblich ist. Die Datei muss den Namen org.keycloak.email.EmailSenderProviderFactory haben und im Verzeichnis src/main/resources/META-INF/services liegen. Als Inhalt hat sie nur eine Zeile mit dem vollständignen Namen der Catory-Klasse: io.github.conciso.keycloak.email.EmailPrefixSenderProviderFactory (Abb. 2).

Dateistruktur des Maven-Projekts. (Abb. 2)

Durch den Maven-Build wird über den Befehl mvn clean install aus diesen Code-Artefakten eine JAR-Datei erzeugt.

Deployment in einem Docker-Container

Die JAR-Datei muss in ein Wildfly-Modul eingepackt werden, um sie nach Keycloak deployen zu können. Das Wildfly-Modul besteht im Prinzip aus einer Descriptor-Datei im XML-Format und der JAR-Datei selbst. Die Descriptor-Datei sieht dabei wie in (Listing 5) gezeigt aus und trägt den Namen module.xml. Sie liegt im Verzeichnis src/main/docker.

(Listing 5)

Sie definiert die Abhängigkeiten auf andere Keycloak-spezifische Wildfly-Module. Diese Wildfly-Module entsprechen den zuvor genannten Maven-Dependencies. Darüber hinaus definiert das Modul aus welchen JAR-Dateien es besteht. In diesem Fall wir die Maven-Property project.build.finalName genutzt, um den Namen der JAR-Datei anzugeben, die durch den Maven-Build erzeugt wird. Dazu ist es notwendig in Maven das Resource-Filtering zu aktivieren (Listing 6).

(Listing 6)

Das Deployment erfolgt durch Kopieren der beiden Dateien in das modules Verzeichnis des Keycloak- bzw. Wildfly-Servers. Nutzt man Keycloak auf Basis der offiziellen Docker-Images, kann das im Rahmen der Erstellung eines Dockerfile geschehen. wie in (Listing 7) beschrieben.

(Listing 7)

Das Image kann nun mittels Docker gebaut werden. Es geht aber auch direkt innerhalb des Maven-Builds mittels Fabric8s-Docker-Maven-Plugin (Listing 8).

(Listing 8)

Der Provider ist damit in Keycloak deployt. Damit er jedoch genutzt wird, muss er noch registriert bzw. geladen werden. Dazu wird das erstellt Wildfly-Modul im Sub-Modul keycloak-server als Povider ergänzt. Dies erfolgt über ein Command-Line-Interface-Skript (CLI) beim Starten des Containers (Listing 9).

(Listing 9)

Das Skript registriert das Wildfly-Modul als Provider. Keycloak wird daraufhin beim Starten das Modul nach möglichen SPI-Implementierungen durchsuchen und diese verfügbar machen. Für die automatisierte Ausführung des CLI-Skripts, stellt das offizielle Docker-Image einen Mechanismus bereit. Das Skript muss lediglich im Verzeichnis /opt/jboss/startup-skript/ hinterlegt werden. Das kann durch Mounten eines Volumes oder durch Kopieren des Skriptes in das Image erfolgen. Im Rahmen des Maven-Builds kann das Skript im Verzeichnis src/main/docker/startup-scripts liegen und mittels Dockerfiles in das Image kopiert werden (Listing 10).

(Listing 10)

Nach einem erneuten Build ist das Docker-Image einsatzbereit. Danach muss man noch den laufenden Docker-Container stoppen, mit Maven erneut den Build starten und anschließend den Container mit dem neuen Image wieder starten (Listing 11). Auf der Server-Info-Page sollte nun neben dem default Provider zusätzlich der neue Provider mit Namen example-email erscheinen.

(Listing 11)

Aktivierung des Providers

Der Keycloak-Server verfügt nun über zwei Implementierungen des gleichen SPI. Es stellt sich daher die Frage, welche Implementierung herangezogen wird, wenn die SPI genutzt werden soll. Hierzu stellt Keycloak die Möglichkeit zur Aktivierung einzelner Provider im Sub-Modul keycloak-server bereit. Dazu wird das CLI-Skript einfach erweitert (Listing 12).

(Listing 12)

Im ersten Schritt wird die SPI emailSender hinzugefügt. Anschließend wird der neue Provider für die SPI ergänzt und aktiviert (enabled=true). Zu guter Letzt setzt ds Skript den Standard-Provider auf den neuen Provider. Das sorgt dafür, dass der Provider immer herangezogen wird, wenn aus Keycloak heraus eine E-Mail versandt werden soll.

Logging

Beim Starten des Containers ist der Provider nun aktiviert und wird standardmäßig verwendet. Zur Prüfung wird zunächst einmal ein Logging in den Provider eingebaut. Keycloak nutzt dazu JBoss-Logging. Dazu muss die entsprechende Dependency in der pom.xml für den Maven-Build ergänzt werden (Listing 13).

(Listing 13)

Zusätzlich ist es notwendig, das entsprechende Wildfly-Modul in der module.xml zu referenzieren (Listing 14). Anschließend kann in der Implementierung des Providers das Logging erfolgen (Listing 15).

(Listing 14)

(Listing 15)

Nach erneutem Build und einem Neustart des Containers ist es möglich die Nutzung des Providers im Logfile zu beobachten. Mittels docker logs -f keycloak kann das Logfile verfolgt werden. Zum Testen wird in der Administrationskonsole dem angemeldeten Testbenutzer über den Menüpunkt Users eine E-Mail-Adresse zugewiesen. Dazu noch den Benutzer suchen, editieren, die E-Mail-Adresse im Feld Email eintragen und die Option Email Verified aktivieren und anschließend speichern (Abb. 3). Im Anschluss muss man noch im Menü Realm Settings unter Email auf Test connection klicken. Im Log sollte dann der entsprechende Eintrag Sending email erscheinen.

Aktivierung der E-Mail für einen Benutzer (Abb. 3)

Nutzung vorhandener Provider

Nun fehlt noch die Implementierung der eigentlichen Funktion zum E-Mail-Versand. Hierzu kann auf die Keycloak Default-Implementierung zurückgegriffen werden. Das kann auf eine von zwei Arten erfolgen: Durch direkte Instanziierung oder Provider-Lookup.

Direkte Instanziierung

In der ersten Variante wir der Default-Provider einfach instanziiert. Die dazu notwendige Java-Klasse org.keycloak.email.DefaultEmailSenderProvider befindet sich im Keycloak-Modul keycloak-services. Dieses wird daher zunächst als Maven-Dependency in der pom.xml ergänzt. Darüber hinaus muss auch diesmal in der module.xml das entsprechende Modul referenziert werden (Listing 16). Anschließend kann der Provider instanziiert und aufgerufen werden (Listing 17).

(Listing 16)

(Listing 17)

Dazu benötigt der DefaultEmailSenderProvider eine KeycloakSession, über die er die notwendingen Einstellungen für den SMTP-Server sowie Absenderadresse etc. ermitteln kann. Diese Informationen entsprechen den Einstellungen in der Administrationskonsole under Realm Settings im Unterpunkt Email. Die KeycloakSession erhält der EmailPrefixSenderProvider ebenfalls über seinen Konstruktor. Entsprechend muss die EmailPrefixSenderProviderFactory angepasst werden (Listing 18). Alternativ könnte eine Instanz des DefaultEmailSenderProvider auch über die zugehörige Factory mittels newDefaultEmailSenderProviderFactory().create(session) erzeugt werden.

(Listing 18)

Provider-Lookup

Keycloak nutzt diese Provider-Factories selbst auch, um entsprechende Instanzen zu erzeugen. Dabei stehen über das Interface an der org.keycloak.provider.ProviderFactory weitere Methoden zur Verfügung, wie zum Beispiel init oder postInit, die hauptsächlich der weiteren Konfiguration der Provider dienen. Es empfiehlt sich daher grundsätzlich eher einen Provider-Lookup in Keycloak zu machen, anstatt eine direkte Instanziierung vorzunehmen. Der Provider-Lookup erfolgt über die KeycloakSession wie mittels session.getProvider(EmailSenderProvider.class, „default“).

Dabei gibt der erste Parameter EmailSenderProvider.class die Art des Providers an, der zweite Parameter „default“ referenziert den Namen der Provider-Implementierung die genutzt werden soll. Die KeycloakSession stellt darüber hinaus viele weitere Methoden für den Zugriff auf die unterschiedlichste Provider bereit. Hier empfiehlt sich grundsätzlich ein Blick in die Java-Dokumentation.

Konfiguration des Providers

Der Mechanismus, der über die ProviderFactory bereitgestellt wird, kann auch für die Konfiguration eigener Provider genutzt werden. Als Beispiel dient das Präfix für die Betreffzeile der E-Mail. Diese ist bislang hart als _“KEYCLOAK: „_ kodiert. Um sie zu konfigurieren, wir der Provider erweitert, so dass das Präfix über den Konstruktor an den Provider übergeben werden kann (Listing 19).

(Listing 19)

Der Wert wird analog zur KeycloakSession über die Factory ermittelt. Hierzu kann mittels der init Methode in der Factory auf die Konfiguration der Provider-Implementierungen zugegriffen werden (Listing 20).

(Listing 20)

Die Provider-Factory definiert nun, dass unter dem Schlüssel subjectPrefix das entsprechende Präfix in der Konfiguration zu finden ist. Letztlich wird der Konfigurationseintrag über CLI-Skripte im Keycloak-Sub-System für die EmailSenderProvider SPI vorgenommen. Dazu ist lediglich eine Anpassung im bereits vorgestellten CLI-Skript notwendig. Die Zeile, die den Email-Prefix-Provider hinzugefügt , wird um das entsprechene Property in der Konfiguration erweitert (Listing 21).

(Listing 21)

Nach dieser Konfigurationsänderung werden nun alle E-Mails mit dem Präfix „SSO: „ im Betreff verschickt. Auf diese Art und Weise können SPIs konfiguriert werden. Neben einfach Strings, werden auch Integers, Listen und boolesche Werte unterstützt. Darüber hinaus ist es möglich, Ausdrücke zu nutzen, um System-Properties wie ${subjectPrefix} oder Umgebungsvarianten wie §{env.SUBJECT\_PREFIX_} aufzulösen und Standardwerte zu setzen. Die zuvor genannte Konfiguration liest so beispielsweise die Umgebungsvariable SUBJECT_PREFIX. Ist diese nicht gesetzt, wird das Präfix auf KEYCLOAK gesetzt. Wie in vielen Container-Umgebungen heute üblich, ist damit prinzipiell auch die Konfiguration über Umgebungsvariablen möglich (Listing 22).

(Listing 22)

Fazit

Das vorgestellte Beispiel zeigt, wie einfach Erweiterungen mittels Service-Provider-Interfaces in Keycloak möglich sind. Das Beispiel ist recht trivial gewählt. Die Vielfalt an vorhandenen SPIs, wie in der Server-Info-Page ersichtlich, lässt weitaus umfangreichere Anpassungsmöglichkeiten erahnen. Dabei folgen solche Anpassungen in der Regel den vorgestellten Mustern. Dazu zählt im Kern die Implementierung der SPI durch einen konkreten Provider mit zugehöriger Factory. Ebenso ist die Registrierung der SPI und deren Konfiguration vor allem in komplexeren Szenarien zumeist notwendig. Gleiches gilt für Aspekte wie Logging oder die Nutzung vorhandener Provider über entsprechende Lookups. Sie bilden daher die Grundlagen für eigene Erweiterungen eines Keycloaks.Systems.

Der Autor Sven-Torben Janus hat zu diesem Thema einen Vortrag bei der JUG Dortmund gehalten.

 

Sven-Torben Janus

Sven-Torben Janus arbeitet als Principal Architect bei der Conciso GmbH wo er als Partner den Bereich Softwarearchitektur mitverantwortet. Er befürwortet einen agilen und praktikablen Entwurf von Softwarearchitekturen und ist Gründer des DDD Meetups Rhein/Main.

 

 

Redaktion


Leave a Reply