Vom WildFly-Erbe zur schlanken Quarkus-Distribution: was sich seit der großen Architektur-Migration geändert hat, wie ein produktives Cluster mit Infinispan und externer PostgreSQL aufgebaut ist, wie sich Themes für Login und E-Mail anpassen lassen, und wie Realm-Exports den Weg zu GitOps-fähiger IAM-Konfiguration freimachen. Ein zehnseitiger Tiefenartikel zu dem Werkzeug, das wir in unseren Kundenprojekten in regulierten Branchen am häufigsten antreffen.
Einordnung — das IAM für regulierte Branchen
Keycloak ist eine quelloffene Identity- und Access-Management-Plattform unter Apache-2.0-Lizenz, hervorgegangen aus einem JBoss-Projekt von Red Hat. Die kommerzielle Schwester unter dem Namen Red Hat build of Keycloak (vormals RH-SSO) wird in den allermeisten regulierten Setups in Deutschland eingesetzt — Banken, Versicherungen, Energieversorger und die öffentliche Verwaltung haben sich auf das Werkzeug verständigt, weil es self-hosted betrieben werden kann und alle gängigen Standards (OIDC, OAuth 2.0, SAML 2.0) abdeckt.
Drei Eigenschaften machen Keycloak in unseren Projekten zum De-facto-Standard. Erstens: self-hosted und EU-konform. Wo eine SaaS-Lösung wie Auth0 oder Okta an Datenschutz- und KRITIS-Anforderungen scheitert, läuft Keycloak im eigenen Rechenzentrum oder in der EU-Cloud. Zweitens: standardkonform und protokollreich. OIDC mit allen Flows (Authorization Code mit PKCE, Client Credentials, Device Code, CIBA), SAML als Identity Provider und Service Provider, dazu Identity Brokering zu externen IdPs und User Federation gegen LDAP oder Active Directory. Drittens: reif und gepflegt. Seit 2014 produktiv, hinter den Kulissen von Red Hat finanziell abgesichert, mit einer großen Open-Source-Community und monatlichen Patch-Releases.
Eine der wichtigsten Zäsuren in der Geschichte des Projekts liegt bei Version 17 (April 2022), mit der die Auslieferung von WildFly auf Quarkus umgestellt wurde. Die Quarkus-Distribution startet in unter zwei Sekunden, hat einen deutlich kleineren RAM-Footprint und konfiguriert sich über eine einzige Datei (keycloak.conf) oder Umgebungsvariablen — eine spürbare Vereinfachung gegenüber dem alten WildFly-Setup mit seinen XML-Subsystem-Dateien. Wer heute neu aufsetzt, sollte ausschließlich die Quarkus-Distribution einsetzen; die WildFly-Variante wird seit Version 20 nicht mehr unterstützt. Viele unserer Beratungseinsätze beginnen genau an dieser Stelle: ein Bestandskunde hat noch die alte WildFly-Installation und braucht einen sauberen Migrationspfad.
Was Keycloak nicht ist, sollte ebenfalls klar benannt sein. Es ist keine Customer-Identity-Plattform mit fertigem Marketing-Tooling — wer ein Auth0 mit Drag-and-Drop-Login-Flows, A/B-Tests und vorgefertigten Social-Login-Buttons für ein Consumer-Produkt sucht, ist dort besser aufgehoben. Keycloak verlangt ein Mindestmaß an Engineering: Realm-Design, Theme-Anpassung, Cluster-Betrieb, Upgrade-Disziplin. Im Gegenzug bekommt man ein Werkzeug, das in der eigenen Hand bleibt und keine Vendor-Bindung erzeugt.
Kernkonzepte — Realm, Client, User, Rolle
Fünf Konzepte halten Keycloak zusammen. Wer sie unterscheidet, hat das mentale Modell — der Rest ist Konfiguration und Theme-Arbeit.
Realm — der Mandanten-Container
Ein Realm ist der oberste logische Container. Innerhalb eines Realms existieren Users, Clients, Rollen, Groups und Themes — vollständig isoliert von anderen Realms. Multi-Tenant-Setups bilden Mandanten typischerweise als eigene Realms ab: ein „mitarbeiter"-Realm für die interne Belegschaft, ein „kundenportal"-Realm für externe Endnutzer, gegebenenfalls je Geschäftsbereich oder Marke ein weiterer Realm. Der Master-Realm hat eine Sonderrolle: er verwaltet die Server-Konfiguration und die Realm-Administratoren — Endbenutzer haben dort nichts zu suchen.
Client — die registrierte Anwendung
Ein Client ist eine bei Keycloak registrierte Anwendung, die Authentifizierung in Anspruch nimmt. OIDC-Clients erhalten eine Client-ID und (für vertrauenswürdige Backend-Clients) ein Client-Secret. Vier wichtige Unterscheidungen: Public Clients (Browser-SPAs, Mobile-Apps — kein Secret, nutzen PKCE), Confidential Clients (Server-zu-Server, mit Secret oder mTLS), Bearer-Only-Clients (Resource Server, die nur Tokens validieren), und Service-Account-Clients (Machine-to-Machine ohne Endbenutzer). SAML-Clients gibt es zusätzlich für Legacy- oder B2B-Integrationen, in denen SAML weiterhin dominant ist.
User, Rolle, Group
Ein User ist eine Identität in einem Realm. Rollen sind die Berechtigungseinheiten — sie gibt es auf zwei Ebenen: Realm Roles gelten realm-weit (etwa „realm-admin", „auditor"), Client Roles sind an einen konkreten Client gebunden (etwa „order-service.viewer", „order-service.editor"). Groups sind Sammlungen von Usern, die gemeinsame Attribute und Rollen erben. Eine Group „abteilung-bestellwesen" mit zwei zugewiesenen Client Roles vererbt diese automatisch an alle Mitglieder — der Standardweg für skalierbares Berechtigungsmanagement.
Composite Roles sind ein häufig übersehenes Feature: eine Rolle kann andere Rollen einschließen. „order-service.admin" als Composite Role kann „order-service.viewer" und „order-service.editor" implizit einschließen — Tokens des Users enthalten dann alle drei Rollen. Damit lassen sich Berechtigungshierarchien sauber modellieren, ohne dass auf Anwendungsseite Rollenlogik nachgebaut werden muss.
Authentication Flow
Der Authentication Flow ist die konfigurierbare Kette von Schritten, die bei der Anmeldung durchlaufen wird: Username + Passwort → optional MFA → optional Risiko-Bewertung → Token-Ausgabe. Keycloak bringt Standard-Flows mit, die für 95 Prozent der Setups ausreichen — Anpassungen sind aber jederzeit möglich, etwa um eine zweistufige MFA-Pflicht für administrative Konten einzuführen, ohne andere User zu belasten.
Authentifizierungs-Flows in der Praxis
Welcher Flow für welche Anwendung gewählt wird, entscheidet später über Sicherheit und Wartbarkeit. Die richtige Wahl ist eine der wenigen wirklich folgenreichen Architektur-Entscheidungen rund um Keycloak.
Authorization Code mit PKCE — der Standard für interaktive Anwendungen
Egal ob klassische serverseitig gerenderte Web-App oder Single-Page-Anwendung: der Authorization Code Flow mit PKCE (Proof Key for Code Exchange) ist heute die richtige Wahl. Der ältere Implicit Flow ist nach OAuth 2.1 obsolet — wer ihn in alten Anwendungen findet, hat einen Migrationsfall vorliegen. PKCE schützt gegen Authorization-Code-Interception und ist auch für vertrauliche Clients empfehlenswert; in Public Clients (Browser, Mobile) ist er ohnehin Pflicht.
Client Credentials — Service-zu-Service
Wenn zwei Backend-Services miteinander reden und kein Endnutzer involviert ist, ist der Client Credentials Flow richtig. Der Service authentisiert sich mit seinem Client-Secret (oder mTLS-Zertifikat) und erhält ein Access Token mit den Berechtigungen seines Service-Accounts. In Microservice-Architekturen ein Brot-und-Butter-Pattern.
Geräte ohne Browser oder mit eingeschränkter Eingabe nutzen den Device Authorization Flow: der User bekommt einen kurzen Code angezeigt und gibt ihn auf einem anderen Gerät (Smartphone) ein, das den Browser-Login durchführt. Häufig auch für CLI-Tools genutzt — die az- oder gh-CLIs sind bekannte Beispiele aus dem nicht-Keycloak-Umfeld.
Eine im Banking-Umfeld zunehmend relevante Variante: der Endpunkt ist nicht der Benutzer-Browser, sondern eine Mitteilung an dessen Smartphone (Push-Notification mit Bestätigungsdialog). CIBA ist Teil des FAPI-2.0-Profils und wird in PSD2/PSD3-konformen Banking-APIs künftig der Standard sein.
SAML 2.0 — Legacy und B2B
Trotz aller OIDC-Dominanz lebt SAML weiter — vor allem in B2B-Föderationen, im öffentlichen Sektor und in Enterprise-SaaS-Anbindungen. Keycloak fungiert als SAML Identity Provider (für externe Service Provider) und als SAML Service Provider (für ein externes Identity Provider). Die Konfiguration ist umfangreicher als bei OIDC; einmal eingerichtet, läuft sie aber stabil.
Cluster-Architektur
Ein produktives Keycloak-Setup besteht aus mehreren Komponenten, die zusammen die Hochverfügbarkeit, den geteilten Cache-Zustand und die persistente Datenhaltung tragen. Die folgende Abbildung zeigt die typische Topologie für eine Single-Site-Installation.
Abbildung 1 — Drei-Knoten-Cluster mit Reverse Proxy, Infinispan-basierter Cache-Replikation und externer PostgreSQL als Konfigurations- und Identitätsspeicher. Optional dient ein LDAP- oder Active-Directory-Server als User-Federation-Quelle. Jeder Knoten hält einen synchronisierten Cache; bei Ausfall eines Knotens leiten Reverse Proxy und Infinispan die Sessions automatisch auf die verbleibenden Knoten um.
Was Infinispan tatsächlich speichert
Die Cache-Schicht in Keycloak hält drei Arten von Daten: Realm-Cache (Realm-Definitionen, Client-Konfigurationen — selten geändert, häufig gelesen), Session-Cache (aktive User-Sessions, Refresh-Tokens, Authorization Codes — zentral für Stateful-Verhalten), und Login-Failures (für Brute-Force-Schutz). Die ersten beiden werden in einem distributed-Cache-Modus über alle Knoten verteilt; jede Information liegt auf mehreren Knoten und überlebt einen Knoten-Ausfall.
Stickiness am Reverse Proxy
Auch wenn der Infinispan-Cluster jede Session auf jedem Knoten verfügbar macht, hat sich in der Praxis Session-Affinität bewährt — der Reverse Proxy leitet einen User möglichst immer auf denselben Knoten. Performance-Gründe (Cache-Hit-Raten) sind der eine Aspekt; der andere ist die Stabilität bei längeren OIDC-Flows mit mehreren Schritten (Login, MFA, Consent), die auf einem konsistenten Knoten reibungsloser laufen.
Cross-Datacenter
Für Multi-Site-Setups bietet Keycloak einen Multi-Site-Mode mit aktiv-aktiv-Replikation über zwei Rechenzentren. Der Aufbau ist anspruchsvoller — Infinispan über RPC, Postgres-Replikation, abgestimmtes Failover-Verhalten. In den meisten Tenvias-Projekten ist ein Single-Site-HA-Setup mit drei Knoten ausreichend; Cross-DC wird konkret beim Banking-Kunden mit echtem Disaster-Recovery-Anspruch relevant.
Installation und Konfiguration der Quarkus-Distribution
Eine produktionsnahe Konfiguration besteht aus drei Bausteinen: der Quarkus-Distribution, einer externen PostgreSQL-Datenbank und einer durchdachten Konfigurationsdatei. Die folgenden Schritte zeigen ein lauffähiges Single-Node-Setup, das sich direkt zum HA-Cluster ausbauen lässt.
Voraussetzungen
Java 17 oder neuer, ein externer PostgreSQL-Server (mindestens 13, idealerweise 15+), ein FQDN mit gültigem TLS-Zertifikat hinter einem Reverse Proxy. Die mitgelieferte kc.sh-Datei ist das Standard-Werkzeug für Start, Build und Migration.
Konfigurationsdatei keycloak.conf
Alle Einstellungen leben in conf/keycloak.conf. Die wichtigsten Blöcke:
Sensible Werte (Passwörter, Client Secrets) gehören nicht in die Datei, sondern in Umgebungsvariablen mit dem KC_-Präfix. db-password wird so über KC_DB_PASSWORD gesetzt. In Kubernetes typischerweise als Secret eingebunden.
Build-Optimierung
Eine Eigenheit der Quarkus-Distribution: vor dem produktiven Start gehört ein kc.sh build-Schritt, der die statischen Einstellungen (Datenbanktreiber, aktivierte Features) in eine optimierte Distribution kompiliert. Wer das vergisst, sieht beim Start eine Warnung und einen langsameren Boot. In Container-Images macht man das einmal beim Image-Bauen:
FROM quay.io/keycloak/keycloak:26.0 as builder
ENV KC_DB=postgres
ENV KC_HEALTH_ENABLED=true
ENV KC_METRICS_ENABLED=true
RUN /opt/keycloak/bin/kc.sh build
FROM quay.io/keycloak/keycloak:26.0
COPY --from=builder /opt/keycloak/ /opt/keycloak/
ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]
CMD ["start", "--optimized"]
Initialer Admin-User
Beim allerersten Start sucht Keycloak einen Bootstrap-Admin in den Umgebungsvariablen KC_BOOTSTRAP_ADMIN_USERNAME und KC_BOOTSTRAP_ADMIN_PASSWORD. Direkt nach dem ersten Login wird ein regulärer Admin-User angelegt und der Bootstrap-Pfad deaktiviert. In Produktion sollte das initiale Passwort nach genau diesem Schritt rotiert werden.
Kubernetes mit Operator
Für Kubernetes-Setups ist der offizielle Keycloak Operator der etablierte Weg. Er übernimmt das Cluster-Scaling, die Postgres-Verbindung und die Realm-Konfiguration über CRDs. Eine Minimal-Definition:
Der Operator startet drei Keycloak-Pods mit Infinispan-Discovery über das Kubernetes-Stack, lädt das DB-Passwort aus dem Secret und bindet alles an eine Ingress-Definition. Realm-Konfigurationen lassen sich über die zusätzliche CRD KeycloakRealmImport deklarativ pflegen — der Weg zu GitOps-fähigem IAM.
Themes und Custom Branding
Eines der unterschätzten Themen in Keycloak: das mitgelieferte Login-Frontend sieht aus wie Keycloak — und das ist in den meisten Kundenprojekten nicht das, was die Geschäftsseite akzeptiert. Themes erlauben ein vollständiges Re-Branding ohne Eingriff in den Server-Code.
Theme-Typen
Keycloak kennt vier Theme-Typen, die alle separat angepasst werden können: login (Login-Seiten, Passwort-Reset, Consent), account (die Account-Konsole, in der Endbenutzer ihr eigenes Profil verwalten), email (E-Mail-Vorlagen für Bestätigungs- und Reset-Mails), und admin (die Admin-Konsole — wird in produktiven Setups praktisch nie angepasst). Für ein Kunden-Branding reichen in den meisten Fällen login und email aus.
Theme-Verzeichnisstruktur
Ein Theme lebt im Verzeichnis themes/<theme-name>/<typ>/ mit einer klaren Substruktur:
Die theme.properties jedes Theme-Typs erlaubt es, von einem Parent-Theme zu erben — typischerweise parent=keycloak. Damit überschreibt das eigene Theme nur die Dateien, die sich tatsächlich unterscheiden, und behält den Standard-Fallback für den Rest. Wer ein Login-Theme „from scratch" bauen will, sollte die nächste Stunde planen und dann doch auf die Parent-Vererbung umstellen — die Anzahl der zu überschreibenden FTL-Templates ist größer als gedacht.
FreeMarker als Template-Engine
Die Templates sind in FreeMarker (.ftl) geschrieben. Wer FreeMarker noch nicht kennt: die Syntax liegt zwischen JSP und Thymeleaf; ${...} für Variablen, <#if ...> für Bedingungen, <#list ...> für Schleifen. Die wichtigste Datei eines Login-Themes ist template.ftl, die das Grundgerüst (Header, Footer, Logo, CSS-Includes) definiert; die einzelnen Seiten (login.ftl, register.ftl, login-update-password.ftl) erben davon.
Theme-Deployment
Im klassischen Modell legt man das Theme-Verzeichnis unter themes/ ab und startet Keycloak neu. Für GitOps-Setups verpackt man das Theme als JAR mit einer META-INF/keycloak-themes.json und kopiert es in das providers/-Verzeichnis — mit einem kc.sh build wird es registriert. Das ist auch der Weg, der mit dem Keycloak Operator zusammenarbeitet, weil das Theme als Container-Layer im Image landet.
Praxis-Hinweis
Halten Sie Theme-Anpassungen minimal. Jedes überschriebene FTL-Template bindet Sie an eine Keycloak-Version — bei Major-Upgrades kann sich die zugrundeliegende Template-Struktur ändern, und Ihr Theme muss nachgezogen werden. Wer nur CSS und ein paar Texte anpasst, kommt fast verlustfrei durch Upgrades. Wer ganze HTML-Strukturen umgeschrieben hat, wird das bei jedem Major-Schritt bereuen.
Integration in Anwendungen
Die Anbindung an Keycloak ist auf der Anwendungsseite überraschend einfach, sobald die richtigen Bibliotheken eingesetzt werden. Die schwierigeren Themen liegen bei Reverse-Proxy-Headern und der Token-Validierung.
Spring Boot — Resource Server
Ein Spring-Boot-Backend, das Tokens validiert, braucht den OAuth2-Resource-Server-Starter und eine einzige Konfigurationszeile:
Das JWKS-Endpoint wird automatisch über die OIDC-Discovery (.well-known/openid-configuration) gefunden. Token-Signaturen werden gegen die öffentlichen Schlüssel von Keycloak validiert; bei Schlüsselrotation lädt Spring die neuen Schlüssel automatisch nach. In der Java-Konfiguration werden anschließend Rollen aus dem Token gemappt (typischerweise aus realm_access.roles oder resource_access.<client>.roles).
Frontend — Single-Page-Anwendung
Für moderne Frontends (React, Vue, Angular) ist die Library oidc-client-ts der Standardweg. Sie übernimmt den Authorization-Code-Flow mit PKCE, das Token-Refreshing und die Logout-Logik:
Wichtig: PKCE ist in oidc-client-ts standardmäßig aktiv — kein Client Secret im Browser. Das ist genau die richtige Voreinstellung; alle Versuche, ein Client Secret aus einem Browser zu nutzen, sind sicherheitstechnisch sinnlos.
Reverse Proxy und X-Forwarded-Header
Eine der häufigsten Stolpersteine: Keycloak hinter einem Reverse Proxy „sieht" sich selbst auf der internen HTTP-URL, die Browser sehen ihn aber unter der externen HTTPS-URL. Wer das nicht regelt, baut OIDC-Redirect-URLs zusammen, die der Browser ablehnt. Drei Konfigurationen müssen zusammenspielen:
Keycloak:proxy-headers=xforwarded, hostname=auth.intern.example.de, hostname-strict-https=true in keycloak.conf.
Reverse Proxy (Nginx):proxy_set_header X-Forwarded-Proto https; und X-Forwarded-Host, X-Forwarded-For entsprechend setzen.
Container-Netzwerk: die internen Adressen müssen für die Trusted-Proxy-Liste freigeschaltet sein, damit Keycloak die Header überhaupt akzeptiert.
Sobald diese drei Stellen aufeinander abgestimmt sind, ist der Rest reibungsfrei. Wenn nicht, sieht man stundenlang Redirect-Fehler ohne erkennbaren Grund.
Federation, Brokering und FAPI
Über die Standard-Anwendungsfälle hinaus bietet Keycloak drei Erweiterungsbereiche, die in regulierten Branchen regelmäßig zum Tragen kommen.
Identity Brokering — fremde IdPs als Login-Quelle
Identity Brokering bedeutet: Keycloak delegiert die eigentliche Authentifizierung an einen anderen IdP, übernimmt aber die Identität und Token-Ausgabe nach eigenen Regeln. Klassische Beispiele: Login per Google, GitHub oder Microsoft Entra ID für externe Mitarbeitende; BundID oder ELSTER-Login für Anwendungen im öffentlichen Sektor; Microsoft Entra ID als Föderation für M365-Konten. Keycloak ist Service Provider und übersetzt die Antwort des externen IdP in eigene Tokens — die Anwendung dahinter bekommt einheitliche Tokens, unabhängig davon, woher der User ursprünglich kam.
User Federation — externe Identitätsspeicher
User Federation ist ein anderes Konzept: hier liegt die Benutzerdatenbank in einem externen System (LDAP oder Active Directory), Keycloak greift darauf zu, um Authentifizierungen durchzuführen. Drei Modi: read-only (Keycloak liest, schreibt nichts zurück — der LDAP-Server bleibt die alleinige Quelle der Wahrheit), writable (Änderungen am User in Keycloak werden in das LDAP zurückgeschrieben), import (Users werden einmal importiert und ab dann lokal verwaltet). In den allermeisten Tenvias-Projekten ist read-only die richtige Wahl — das AD bleibt die führende Identitätsquelle, Keycloak ergänzt nur die OIDC-/SAML-Sicht darüber.
FAPI — Financial-Grade API
FAPI 1.0 Advanced und FAPI 2.0 sind Sicherheitsprofile des OpenID-Konsortiums, die Banking-APIs unter PSD2 und PSD3 erfüllen müssen. Keycloak unterstützt FAPI über vorkonfigurierte Client-Profile, die strenge Mindestanforderungen erzwingen: PAR (Pushed Authorization Requests), DPoP oder mTLS für Sender-Constrained-Tokens, asymmetrische Client-Authentifizierung über Private-Key-JWT statt Client Secret. Wer eine Banking-API anbietet, kommt um FAPI nicht herum — und Keycloak ist eines der wenigen Open-Source-IdPs, das die Anforderungen vollständig abdeckt.
Customizing in der Praxis — Schritt für Schritt
Zwei Anpassungen tauchen in praktisch jedem Keycloak-Projekt auf, das wir begleiten: die Umgestaltung der Login-Seite an das Kunden-Branding und die Anbindung einer externen User-Quelle, die weder LDAP noch Active Directory ist. Beide Aufgaben lassen sich sauber lösen, sobald man den Werkzeugkasten verstanden hat. Die folgenden zwei Anleitungen führen Schritt für Schritt durch — die erste mit FreeMarker und CSS, die zweite mit Java-Code für einen eigenen Federation-Provider.
(a) Login-Seite umgestalten
Die mitgelieferte Keycloak-Login-Maske wirkt unverkennbar nach Keycloak. Für Kundenprojekte ist das fast nie akzeptabel — die folgenden sieben Schritte führen zu einer vollständig gebrandedten Variante mit eigenem Logo, eigenen Farben, eigenen Texten und optional einem zusätzlichen Hinweisblock im Formular.
Schritt 1 — Theme-Verzeichnis anlegen. Im Keycloak-Server (oder als Maven-Modul, falls das Theme als JAR ausgeliefert wird) wird folgende Struktur aufgebaut:
Schritt 2 — theme.properties mit Parent-Vererbung. Diese Datei steuert, was vom mitgelieferten Theme geerbt wird und welche eigenen CSS-Dateien geladen werden:
parent=keycloak heißt: alle nicht überschriebenen Templates und Ressourcen werden aus dem mitgelieferten Keycloak-Theme geerbt. Die styles-Zeile listet die CSS-Dateien in der gewünschten Reihenfolge — wichtig ist, die mitgelieferte login.css zuerst und die eigene custom.css danach zu laden, damit die eigenen Regeln die Defaults überschreiben.
Schritt 3 — Brand-spezifisches CSS. Die Datei resources/css/custom.css enthält die Branding-Anpassungen. Ein typisches Beispiel:
Schritt 4 — Eigene Texte und Übersetzungen. Die Standard-Beschriftungen (etwa „Sign In") sollen durch eigene Begriffe ersetzt werden. In messages/messages_de.properties:
loginAccountTitle=Anmeldung bei Tenvias
doLogIn=Anmelden
usernameOrEmail=Benutzername oder E-Mail
password=Passwort
doForgotPassword=Passwort vergessen?
hinweisTitel=Hinweis zur Anmeldung
hinweisText=Diese Anwendung ist nur fuer berechtigte Mitarbeitende. \
Bei Fragen wenden Sie sich an servicedesk@tenvias.de.
Die Schlüsselnamen loginAccountTitle, doLogIn und so weiter sind in Keycloak vordefiniert; eine vollständige Liste findet sich im Quellcode des Keycloak-Standard-Themes. Eigene Schlüssel (im Beispiel hinweisTitel und hinweisText) können in eigenen FreeMarker-Templates über ${msg("hinweisTitel")} verwendet werden.
Schritt 5 — Selektives Überschreiben einer FreeMarker-Datei. Wenn das Standard-Layout nicht reicht, kann jede .ftl-Datei aus dem Parent-Theme einzeln überschrieben werden. Eine angepasste login.ftl, die einen zusätzlichen Hinweisblock über dem Login-Formular einfügt:
Nur die Sektionen, die wirklich überschrieben werden sollen, müssen ausgeschrieben werden — alles andere lebt im Parent-Template weiter.
Schritt 6 — Theme deployen. Bei einem klassischen Setup wird das Verzeichnis nach ${KEYCLOAK_HOME}/themes/ kopiert und Keycloak neu gestartet. Bei einem Container- oder Kubernetes-Setup wird das Theme als JAR verpackt — Verzeichnisstruktur im JAR:
JAR ins providers/-Verzeichnis legen, anschließend kc.sh build ausführen und Keycloak neu starten.
Schritt 7 — Theme im Realm aktivieren. In der Admin-Konsole: Realm Settings → Themes → Login Theme auf tenvias-login stellen. Alternativ deklarativ über den Realm-JSON-Export:
Der nächste Login-Aufruf zeigt die angepasste Maske. Falls die Änderungen nicht sichtbar werden, hilft fast immer ein Browser-Cache-Reload oder das Setzen von spi-theme-cache-themes=false während der Theme-Entwicklung.
(b) Externe Quelle über einen Federation-Provider anbinden
Wenn weder LDAP noch Active Directory die existierende User-Datenbank abdecken — typisch bei Legacy-Anwendungen mit eigener User-Tabelle oder einer eigenen REST-API — schreibt man einen eigenen User Storage Provider in Java. Das folgende Beispiel bindet eine externe REST-API als Federation-Quelle an: Keycloak fragt bei jedem Login die Legacy-API ab, validiert das Passwort dort und stellt die Identität als „normalen" Keycloak-User dar. Wir gehen die sieben notwendigen Schritte durch.
Schritt 1 — Maven-Projekt aufsetzen. Eine pom.xml mit den Keycloak-SPI-Abhängigkeiten als provided:
scope=provided ist entscheidend: die SPI-Bibliotheken bringt Keycloak selbst mit, sie dürfen nicht in das ausgelieferte JAR gebündelt werden, sonst entstehen Class-Loader-Konflikte zur Laufzeit.
Schritt 2 — Provider-Klasse implementieren. Die zentrale Klasse implementiert UserStorageProvider und die Schnittstellen, die das jeweilige Feature abdecken. Für Username-Lookup und Passwort-Validierung sind das UserLookupProvider und CredentialInputValidator:
package de.tenvias.keycloak;
import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialInput;
import org.keycloak.credential.CredentialInputValidator;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.credential.PasswordCredentialModel;
import org.keycloak.storage.StorageId;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.user.UserLookupProvider;
public class LegacyRestStorageProvider
implements UserStorageProvider,
UserLookupProvider,
CredentialInputValidator {
private final KeycloakSession session;
private final ComponentModel model;
private final LegacyApiClient apiClient;
public LegacyRestStorageProvider(KeycloakSession session,
ComponentModel model,
LegacyApiClient apiClient) {
this.session = session;
this.model = model;
this.apiClient = apiClient;
}
@Override
public UserModel getUserByUsername(RealmModel realm, String username) {
LegacyApiClient.User legacyUser = apiClient.findByUsername(username);
if (legacyUser == null) return null;
return new LegacyUserAdapter(session, realm, model, legacyUser);
}
@Override
public UserModel getUserByEmail(RealmModel realm, String email) {
LegacyApiClient.User legacyUser = apiClient.findByEmail(email);
if (legacyUser == null) return null;
return new LegacyUserAdapter(session, realm, model, legacyUser);
}
@Override
public UserModel getUserById(RealmModel realm, String id) {
String externalId = StorageId.externalId(id);
return getUserByUsername(realm, externalId);
}
@Override
public boolean supportsCredentialType(String credentialType) {
return PasswordCredentialModel.TYPE.equals(credentialType);
}
@Override
public boolean isConfiguredFor(RealmModel realm, UserModel user,
String credentialType) {
return supportsCredentialType(credentialType);
}
@Override
public boolean isValid(RealmModel realm, UserModel user,
CredentialInput input) {
if (!supportsCredentialType(input.getType())) return false;
return apiClient.authenticate(
user.getUsername(),
input.getChallengeResponse());
}
@Override
public void close() {
apiClient.close();
}
}
Die Methode isValid ist der eigentliche Sicherheits-Anker: hier wird das vom Browser übergebene Passwort an die Legacy-API weitergereicht, die ihrerseits ein true oder false zurückgibt. Keycloak speichert das Passwort nicht — bei jedem Login wird die Legacy-API erneut befragt.
Schritt 3 — Adapter-Klasse für das User-Modell. Keycloak erwartet ein UserModel. Die meisten Felder werden über die Basisklasse AbstractUserAdapterFederatedStorage automatisch in Keycloak gehalten (Profilfelder, Rollen-Zuweisungen, Group-Mitgliedschaften). Wir überschreiben nur Username, Email, Vor- und Nachname aus der externen Quelle:
package de.tenvias.keycloak;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.storage.StorageId;
import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage;
public class LegacyUserAdapter extends AbstractUserAdapterFederatedStorage {
private final LegacyApiClient.User legacyUser;
public LegacyUserAdapter(KeycloakSession session,
RealmModel realm,
ComponentModel model,
LegacyApiClient.User legacyUser) {
super(session, realm, model);
this.legacyUser = legacyUser;
this.storageId = new StorageId(model.getId(),
legacyUser.getUsername()).getId();
}
@Override
public String getUsername() { return legacyUser.getUsername(); }
@Override
public String getEmail() { return legacyUser.getEmail(); }
@Override
public String getFirstName() { return legacyUser.getFirstName(); }
@Override
public String getLastName() { return legacyUser.getLastName(); }
@Override
public void setUsername(String username) {
throw new UnsupportedOperationException(
"Read-only Federation gegen Legacy-API");
}
}
Read-Only-Federation heißt: Profiländerungen, die ein User in der Account-Konsole von Keycloak macht, werden nicht in die Legacy-API zurückgeschrieben. Wer das umgekehrte Verhalten will, implementiert die Setter-Methoden mit echten API-Aufrufen.
Schritt 4 — ProviderFactory. Die Factory ist der Einstiegspunkt für Keycloak. Sie definiert die Konfigurationsparameter, die der Administrator in der UI sieht, und erzeugt Provider-Instanzen pro Request:
package de.tenvias.keycloak;
import java.util.List;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
import org.keycloak.storage.UserStorageProviderFactory;
public class LegacyRestStorageProviderFactory
implements UserStorageProviderFactory<LegacyRestStorageProvider> {
public static final String PROVIDER_ID = "legacy-rest-storage";
private static final List<ProviderConfigProperty> CONFIG_PROPERTIES =
ProviderConfigurationBuilder.create()
.property()
.name("apiUrl")
.label("Legacy API URL")
.helpText("Basis-URL der externen REST-API")
.type(ProviderConfigProperty.STRING_TYPE)
.defaultValue("https://legacy.intern.example.de/api")
.add()
.property()
.name("apiToken")
.label("API Bearer Token")
.helpText("Token fuer die Authentifizierung gegen die API")
.type(ProviderConfigProperty.PASSWORD)
.secret(true)
.add()
.property()
.name("connectTimeoutMs")
.label("Connect Timeout (ms)")
.type(ProviderConfigProperty.STRING_TYPE)
.defaultValue("3000")
.add()
.build();
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public String getHelpText() {
return "User-Federation gegen eine externe REST-API (Tenvias)";
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return CONFIG_PROPERTIES;
}
@Override
public LegacyRestStorageProvider create(KeycloakSession session,
ComponentModel model) {
String apiUrl = model.get("apiUrl");
String apiToken = model.get("apiToken");
int timeout = Integer.parseInt(
model.get("connectTimeoutMs", "3000"));
LegacyApiClient client = new LegacyApiClient(apiUrl, apiToken, timeout);
return new LegacyRestStorageProvider(session, model, client);
}
}
Der Typ ProviderConfigProperty.PASSWORD sorgt dafür, dass das API-Token in der Admin-Konsole maskiert dargestellt und im Realm-JSON-Export entsprechend behandelt wird — in Setups mit aktiviertem Vault landet dort nur die Vault-Referenz, nicht der Klartext.
Schritt 5 — SPI-Registrierung über die Services-Datei. Damit Keycloak die Factory beim Start findet, wird eine META-INF/services/-Datei mit dem Klassennamen erstellt:
Wer mehrere Factories in einem Modul ausliefert, listet sie zeilenweise untereinander.
Schritt 6 — Bauen und Deployen. Der JAR wird per Maven gebaut und ins providers/-Verzeichnis der Keycloak-Installation kopiert. Anschließend muss Keycloak rebuilden, damit der Provider in die optimierte Distribution aufgenommen wird:
# JAR bauen
mvn clean package
# JAR ins providers-Verzeichnis legen
cp target/legacy-rest-storage-provider-1.0.0.jar \
/opt/keycloak/providers/
# Keycloak rebuilden (registriert den neuen Provider)
/opt/keycloak/bin/kc.sh build
# Starten
/opt/keycloak/bin/kc.sh start
In Container-Setups ist der typische Weg ein Multi-Stage-Dockerfile: eine Maven-Stage baut den JAR, eine zweite Stage kopiert ihn in das offizielle Keycloak-Image und ruft direkt im Build kc.sh build auf. Damit ist der Provider Teil des Image-Inventars und reisefertig.
Schritt 7 — Provider im Realm aktivieren. In der Admin-Konsole: Realm → User Federation → Add Provider. In der Auswahl erscheint legacy-rest-storage — die in der Factory definierten Konfigurationsparameter (URL, Token, Timeout) erscheinen als Eingabefelder. Nach dem Speichern ist die Federation aktiv. Der nächste Login mit einem Username, der in der Legacy-API existiert, durchläuft den vollständigen Flow: getUserByUsername → Provider liefert einen LegacyUserAdapter → isValid validiert das Passwort gegen die Legacy-API → Keycloak stellt einen Token an die anfragende Anwendung aus.
Praxis-Hinweis
Den hier ausgeklammerten LegacyApiClient sollten Sie nicht naiv mit dem JDK-HttpClient und einer Schleife implementieren. In Produktion gehören vier Eigenschaften zwingend dazu: Connection-Pooling (sonst öffnet Keycloak pro Login eine neue TCP-Verbindung), Caching auf User-Lookups mit kurzer TTL von 30–60 Sekunden (entlastet die Legacy-API spürbar), ein Circuit Breaker über Resilience4j (damit eine ausgefallene Legacy-API nicht die gesamte Anmeldeschleife in Keycloak blockiert) und Tracing-Header-Propagation, damit ein Vorfall über das Log-System (siehe unseren Artikel zu Logging im Enterprise-Umfeld) korrelierbar bleibt.
Realm-Lifecycle, Backup und Zero-Downtime-Upgrades
Die laufende Pflege ist der operative Teil, der über Erfolg oder Misserfolg eines IAM entscheidet. Drei Disziplinen sind die wichtigsten.
Realm-Export und -Import als Code
Keycloak kann ein komplettes Realm — inklusive Clients, Rollen, Groups, Authentication Flows, Themes-Referenzen und (optional) Users — in eine JSON-Datei exportieren. Diese Datei ist menschenlesbar, eignet sich für Code-Review und kann in Git versioniert werden. Damit wird das Realm-Modell zu Konfigurations-Code, der in CI-Pipelines ausgerollt werden kann:
# Realm exportieren
kc.sh export \
--dir /tmp/realms \
--realm mitarbeiter \
--users different_files
# Realm importieren (idempotent in den meisten Konstellationen)
kc.sh import \
--file /tmp/realms/mitarbeiter-realm.json
In Kubernetes-Setups übernimmt die KeycloakRealmImport-CRD diesen Schritt deklarativ. Realm-JSONs landen in einem Git-Repository, Pull-Requests werden reviewt, ein CI-Job rollt die geänderte Konfiguration aus. IAM-Konfigurationsänderungen wie „Lehrling X erhält Zugriff auf Verfahren Y" werden damit zu auditierbaren Git-Commits — eine der wichtigsten Compliance-Eigenschaften überhaupt.
Backup-Strategie
Backup heißt für Keycloak drei Dinge gleichzeitig. Erstens: ein vollständiges PostgreSQL-Backup mit pg_dump oder pgBackRest — die DB ist die Quelle der Wahrheit für Realms, Users, Clients, Sessions sind hingegen nur transient. Zweitens: regelmäßiger Realm-Export der wichtigen Realms ins Git-Repository — als zweite, deklarative Sicherung, die auch eine versehentliche Realm-Löschung überlebt. Drittens: ein Backup der Theme-JARs und der keycloak.conf, idealerweise als Teil des Container-Image-Build-Prozesses, sodass das Image selbst reproduzierbar ist.
Zero-Downtime-Upgrades
Keycloak unterstützt Rolling Updates innerhalb derselben Major-Version unproblematisch — alte und neue Knoten arbeiten im selben Infinispan-Cluster zusammen. Der Pfad für ein Major-Upgrade (etwa von 25 auf 26) sieht so aus:
Datenbank-Backup erstellen. Major-Upgrades führen Schema-Migrationen aus, die nicht rückwärtskompatibel sind.
Release-Notes der dazwischenliegenden Versionen lesen — es gibt regelmäßig Deprecations und Migrations-Hinweise, die im laufenden Betrieb relevant sind.
In einer Staging-Umgebung mit einer Kopie der produktiven Datenbank den Upgrade-Schritt durchspielen. Realms öffnen, Test-Login durchführen, Theme-Rendering prüfen.
In Produktion einen Knoten nach dem anderen tauschen. Mit dem Keycloak Operator passiert das automatisch über die Pod-Update-Strategie.
In den meisten unserer Projekte planen wir Major-Upgrades zweimal im Jahr — Keycloak veröffentlicht jährlich vier Major-Versionen, aber n-2 Versionen werden unterstützt. Häufiger upgraden lohnt sich nur, wenn ein konkretes Feature aus einer neuen Version gebraucht wird.
Wann Keycloak passt — und wann eine SaaS-Lösung
Keycloak ist mächtig, etabliert und in regulierten Branchen verbreitet — aber kein Werkzeug für jeden Anwendungsfall. Eine ehrliche Empfehlung am Ende.
Passend, wenn …
der Betrieb self-hosted erforderlich ist — KRITIS, BAIT/VAIT, DORA, NIS2 oder vertragliche Anforderungen verbieten eine SaaS-Auslagerung der Identitäten;
OIDC und SAML nebeneinander gebraucht werden — moderne und Legacy-Anwendungen am selben IdP;
mehrere Realms oder Mandanten getrennt werden müssen — Mitarbeitende, Kunden, B2B-Partner in einer Plattform;
Custom Themes ein eigenes Branding und eine fachlich angepasste Benutzerführung verlangen;
eine vorhandene Identitätsquelle (Active Directory, OpenLDAP, BundID) per Federation oder Brokering eingebunden werden soll;
FAPI-konforme Banking-APIs oder andere hochregulierte Schnittstellen ausgegeben werden;
Realm-Konfiguration als Code im Git-Repository mit Pull-Request-Reviews gepflegt werden soll.
Weniger geeignet, wenn …
eine vollständig gemanagte SaaS-Lösung ohne Eigenbetrieb gewünscht ist — Auth0, Okta, Microsoft Entra External ID bieten dort deutlich weniger operative Tiefe;
das Setup ausschließlich auf Microsoft-Stack aufbaut und Entra ID ohnehin als Mandanten-IdP läuft — dann verdoppelt Keycloak die Komplexität;
das Volumen so klein ist (wenige hundert User, ein einziges Frontend), dass ein gemanagter Dienst wirtschaftlicher ist;
keine Engineering-Kapazität für Upgrade-Disziplin, Cluster-Betrieb und Theme-Pflege vorhanden ist — Keycloak verzeiht Vernachlässigung schlechter als ein SaaS-Werkzeug.
In den meisten regulierten Branchen, mit denen wir arbeiten, ist Keycloak die richtige Wahl — Energieversorger, Banken, Versicherungen, die öffentliche Verwaltung. Die Kombination aus Standardkonformität, Self-Hosting und Open-Source-Lizenz schlägt in dieser Zielgruppe praktisch jede SaaS-Alternative. Im nächsten Artikel dieser Reihe gehen wir auf das dritte Themenfeld ein, das wir in Kundengesprächen besonders häufig diskutieren: Voice-Agenten architektonisch — wie sich ein produktiver Sprachassistent für die kommunale Bürgertelefonie aufbaut, mit ElevenLabs, VAPI und der Anbindung an Anthropic und OpenAI.
Keycloak-Migration oder HA-Aufbau?
Wir prüfen gemeinsam mit Ihrem Team Ihre IAM-Landschaft — Realm-Design, Cluster-Topologie, Postgres-Sizing, Theme-Strategie, Migration vom WildFly- auf die Quarkus-Distribution, FAPI-Anforderungen, GitOps-fähige Realm-Konfiguration. Das Ergebnis: ein konkreter Maßnahmenplan, abgestimmt auf Größe und Reife Ihrer Plattform.