Keycloak vor Active Directory: User Federation, MFA und die typischen Stolpersteine

Worum geht’s

In vielen Unternehmen lebt die Wahrheit über Identitäten in Active Directory. Gleichzeitig sprechen moderne Anwendungen längst OIDC oder SAML — Protokolle, die AD nativ nicht anbietet. Doppelt pflegen ist keine Option. Genau hier setzt Keycloak vor AD an: Keycloak föderiert sich gegen das bestehende AD, übernimmt die Rolle des Identity Providers, ergänzt MFA und liefert die Protokollvielfalt, die AD allein nie liefern wird.

Setup-Ziel: AD bleibt Source of Truth. Keycloak liest die User aus AD, spricht OIDC/SAML mit modernen Anwendungen, layert bei Bedarf MFA darüber — und kommt je nach Anforderung auch im WRITABLE-Modus zurück, wenn User ihr Passwort über Keycloak ändern sollen.

Dieser Artikel bezieht sich auf Keycloak 26. Die Admin-UI ist seit v19 deutlich umgebaut worden; ältere Versionen zeigen einzelne Felder unter anderen Namen.

Architektur-Überblick

Keycloak Active Directory Integration Architecture

  • Anwendungen sprechen mit Keycloak ausschließlich über OIDC oder SAML 2.0 — sie wissen nichts von LDAP oder AD.
  • Keycloak übernimmt Authentifizierung, baut Tokens, layert MFA und steuert die Session-Verwaltung.
  • Active Directory ist die User-Datenbank. Keycloak greift via LDAPS zu, prüft Passwörter dort und liest Attribute/Gruppen.
  • Ein IGA-System wie Saviynt kann darüber das Lifecycle-Management der AD-Identitäten übernehmen — für Keycloak ist das transparent.

Vorbereitung in AD

Bevor irgendetwas in Keycloak klickbar wird:

  1. Dedizierter Service Account in AD anlegen (z. B. keycloak-svc).
  2. Lesezugriff auf die User- und Group-OUs delegieren. Für READ_ONLY reicht das.
  3. Falls WRITABLE geplant ist: zusätzlich Write-Permission auf die relevanten User-Attribute und eine Reset Password-Delegation an die OU.
  4. CA-Zertifikat des Domain Controllers exportieren — wird gleich für den LDAPS-Truststore in Keycloak gebraucht.
  5. Failover-fähig denken: mindestens zwei DCs vorsehen, damit ein DC-Reboot nicht den ganzen IdP lahmlegt.

Tipp: den Service Account aus dem normalen Password-Expiry herausnehmen. Sonst läuft der Bind eines Tages auf, und niemand findet, warum.

Schritt 1 — User Federation Provider anlegen

In der Keycloak Admin Console:

Realm → User Federation → Add LDAP providers

Die wichtigsten Felder im oberen Block:

Vendor: Active Directory

Das ist Schritt Null. Sobald Active Directory als Vendor gewählt ist, befüllt Keycloak alle AD-spezifischen Defaults automatisch — UUID-Attribut auf objectGUID, Object Classes auf person, organizationalPerson, user, und der msad-user-account-control-mapper wird automatisch angelegt. Wer das überspringt, baut den Rest mühsam von Hand.

Connection URL: ldaps://dc1.example.com:636 ldaps://dc2.example.com:636
Bind Type: simple
Bind DN: CN=keycloak-svc,OU=Service Accounts,DC=example,DC=com
Bind credentials: <service-account-password>

Beachten:

  • Mehrere DCs durch Leerzeichen getrennt → Keycloak failovert automatisch. Eine der wichtigsten und am häufigsten vergessenen Einstellungen.
  • LDAPS:636 statt plain LDAP:389. Niemals plain in Produktion.
  • Truststore: bei selbstsigniertem CA-Cert die AD-CA in einen PKCS12-Truststore packen und über KC_TRUSTSTORE_PATHS referenzieren — sonst scheitert der TLS-Handshake mit PKIX path validation failed.

Klick auf Test connection und Test authentication — beides muss grün sein, bevor irgendetwas anderes konfiguriert wird.

Schritt 2 — Edit Mode: READ_ONLY oder WRITABLE?

Diese Entscheidung wird beim Anlegen getroffen und sollte nicht später umgestellt werden. Die Mapper werden bei der Erstellung passend zum Edit Mode generiert.

READ_ONLY — Keycloak liest, schreibt nie zurück.

  • Geeignet, wenn: AD ein eigenes Self-Service-Portal hat (z. B. ein Tool, über das User ihr Passwort zurücksetzen), oder ein IGA wie Saviynt das Lifecycle-Management besitzt.
  • User-Erfahrung: Passwort-Reset und Profil-Updates passieren ausschließlich außerhalb von Keycloak.
  • Vorteil: glasklare Verantwortung — AD ist Master, Keycloak ist reiner IdP.

WRITABLE — Keycloak darf zurückschreiben.

  • Geeignet, wenn: User direkt in Keycloak (z. B. via Account Console) ihr Passwort ändern oder Profil-Felder pflegen sollen.
  • Voraussetzungen: Service Account braucht “Reset Password”-Delegation; AD-Passwort-Policy muss zur Keycloak-Realm-Policy passen (sonst wird ein in Keycloak gesetztes Passwort von AD wieder verworfen).
  • Risiko: zwei Schreibpfade auf dieselbe Identität — Audit-Trails werden unübersichtlicher.

UNSYNCED — Sonderfall: AD ist hart read-only (z. B. anderer Owner), aber Keycloak soll trotzdem lokal User-Daten ändern lassen. Die Änderungen leben dann nur in Keycloak und müssen extern zurücksynchronisiert werden. Selten sinnvoll.

In Setups, in denen ein IGA das Lifecycle besitzt, ist READ_ONLY die richtige Wahl. Wo User ihre Passwörter im Keycloak-Self-Service ändern sollen, ist WRITABLE ehrlicher als ein zweites Self-Service-Portal davor zu stellen.

Schritt 3 — LDAP-Settings im Detail

Users DN:                OU=Users,DC=example,DC=com
Username LDAP attribute: sAMAccountName
RDN LDAP attribute:      cn
UUID LDAP attribute:     objectGUID
User Object Classes:     person, organizationalPerson, user
User Search Filter:      (objectClass=user)

Anmerkungen:

  • Users DN: Default-AD legt User unter CN=Users ab. Viele Organisationen verschieben User in eine eigene OU. Eintragen, was im AD wirklich existiert — Subtree-Search ist Default.
  • Username LDAP attribute: klassisch sAMAccountName für den Domain-Username (john.doe). Je nach AD-Nutzung kann auch userPrincipalName (john.doe@example.com) das passendere Attribut sein — etwa wenn Anwendungen den Login im E-Mail-Format erwarten. Einmal entscheiden und konsistent halten; ein späterer Wechsel verwirrt bestehende Sessions und Mapper.
  • objectGUID ist binär — Keycloak codiert das beim Speichern korrekt, du musst nichts konvertieren.
  • User Search Filter ist optional, aber bei großen ADs sinnvoll, um z. B. deaktivierte Accounts auszufiltern:
    (&(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))
    

Connection-Block:

Enable StartTLS:     false   (bei LDAPS bereits TLS, kein StartTLS nötig)
Use Truststore SPI:  Always
Connection Pooling:  true
Connection Timeout:  5000
Read Timeout:        10000
Pagination:          true

Cache Policy:

Cache Policy: MAX_LIFESPAN
Max Lifespan: 180000     (3 Minuten in ms)

Empfehlung: MAX_LIFESPAN mit 3 Minuten ist ein guter Mittelweg. Ohne Cache fragt Keycloak bei jeder Token-Erzeugung neu gegen LDAPS — bei vielen kurzlebigen Sessions (Service-Token, häufige Refreshs) entstehen so unnötig viele LDAPS-Requests in kurzer Folge und legen sich auf die DCs. 3 Minuten Cache halten den Bind-Throughput klein, ohne dass Gruppen-Mitgliedschafts-Änderungen merklich nachhängen.

Bei extrem volatilen Gruppen ist NO_CACHE möglich — kostet aber sichtbar Performance. Der Default-Wert (DEFAULT, 1h TTL) ist für moderne Authn-Last meist zu lang.

Die Import-Users-Falle

Steht Import Users auf On (Default), schreibt Keycloak die User in die eigene DB und lowercased dabei Username und Email beim Import. Wer in AD John.Doe heißt, landet als john.doe in Keycloak — Login funktioniert weiterhin (sAMAccountName ist case-insensitiv), aber API-Lookups mit dem originalen Case schlagen fehl. Wer das nicht möchte, schaltet Import Users aus — verzichtet damit aber auf lokales Profil-Speichern.

Schritt 4 — Sync-Strategie

Periodic Full Sync:          true         (täglich, fängt Deletions & Drift ab)
Full Sync Period:            86400        (24h)
Periodic Changed Users Sync: true
Changed Users Sync Period:   3600         (stündlich)
Batch Size:                  1000

Empfohlenes Pattern:

  1. Beim Anlegen einmal Synchronize all users klicken (initialer Full Sync).
  2. Periodic Changed Users Sync stündlich aktivieren — überträgt nur Änderungen seit dem letzten Lauf und hält Keycloak nahezu in Echtzeit synchron.
  3. Zusätzlich Periodic Full Sync einmal täglich (idealerweise nachts) laufen lassen — fängt Edge Cases ab, die der Changed-Users-Sync nicht zuverlässig erkennt: gelöschte Accounts, manuell zurückgesetzte Attribute, Drift durch externe Tools oder IGA-Schreibvorgänge auf AD.

Bei sehr großen ADs (>100k User) Batch Size auf 1000-5000 anheben und AD’s MaxPageSize (Default 1000) im Hinterkopf behalten.

Schritt 5 — Mappers konfigurieren

Sobald Vendor=AD beim Anlegen gewählt war, hat Keycloak diese Mapper automatisch erzeugt:

  • usernamesAMAccountName
  • emailmail
  • first namegivenName
  • last namesn
  • msad-user-account-control-mapper ← AD-spezifisch, kritisch

Warum der MSAD-Mapper essentiell ist

Der Mapper liest zwei AD-Attribute aus und übersetzt sie in Keycloak-Verhalten. Beide sind sicherheitsrelevant.

1. Account aktiv oder deaktiviert — Attribut userAccountControl

userAccountControl ist eine Bitmaske, in der AD den Account-Status verwaltet. Das relevante Bit für “Account disabled” ist 0x2. Ein aktiver normaler User hat typischerweise den Wert 512, ein deaktivierter Account 514 (= 512 + 2).

  • Mit Mapper: In AD deaktivierte Accounts werden in Keycloak als disabled markiert, Login wird blockiert.
  • Ohne Mapper: Keycloak akzeptiert weiterhin Logins von in AD deaktivierten Accounts — klassisches Security-Loch.

2. Passwort-Reset erforderlich — Attribut pwdLastSet

pwdLastSet enthält den Zeitpunkt des letzten Passwort-Wechsels. Setzt ein AD-Admin das Attribut auf 0, bedeutet das in AD: “User muss beim nächsten Login das Passwort ändern”.

  • Mit Mapper: Keycloak triggert beim nächsten Login die Required Action Update Password.
  • Ohne Mapper: Der Reset-Wunsch des AD-Admins wird ignoriert; der User loggt sich weiter mit dem alten Passwort ein.

Beide Verhalten sind standardmäßig aktiv, sobald Vendor: Active Directory beim Anlegen des Providers gewählt war. Wer den Mapper manuell deaktiviert oder löscht, verliert diese Sicherheitseigenschaften.

Group LDAP Mapper

Für AD-Gruppen einen neuen Mapper anlegen:

Name:                            ad-groups
Mapper Type:                     group-ldap-mapper

LDAP Groups DN:                  OU=Groups,DC=example,DC=com
Group Name LDAP Attribute:       cn
Group Object Classes:            group
Preserve Group Inheritance:      false
Membership LDAP Attribute:       member
Membership Attribute Type:       DN
Membership User LDAP Attribute:  cn

Mode:                            READ_ONLY
User Roles Retrieve Strategy:    LOAD_GROUPS_BY_MEMBER_ATTRIBUTE_RECURSIVELY
Drop non-existing groups during sync: true

Wichtig — Nested Groups: AD nutzt verschachtelte Gruppen sehr gerne. Die Retrieve Strategy LOAD_GROUPS_BY_MEMBER_ATTRIBUTE_RECURSIVELY löst transitive Mitgliedschaften korrekt auf, indem Keycloak rekursive LDAP-Queries macht — kostet bei tief verschachtelten Gruppen oder großen ADs aber spürbar Performance. Preserve Group Inheritance muss in AD-Setups trotzdem auf false stehen — AD’s Gruppen-Topologie passt nicht zum hierarchischen Group-Tree-Modell von Keycloak.

Von Gruppe zu Rolle

group-ldap-mapper importiert Gruppen — er weist keine Keycloak-Rollen zu. Der Workflow ist:

  1. AD-Gruppe App-Admins existiert → wird via Group Mapper als Keycloak-Gruppe App-Admins importiert.
  2. In Keycloak: Groups → App-Admins → Role mappings → Realm Role admin zuweisen.
  3. Jeder User, der via AD-Sync in App-Admins landet, erbt automatisch die admin-Rolle.

So bleibt die Rolle-Hoheit in Keycloak, die Mitgliedschaft kommt aus AD. Genau das Pattern, das man will: Rollen zu vergeben ist eine Keycloak-Verantwortung, wer welcher Gruppe angehört eine AD-Verantwortung.

Schritt 6 — MFA über Authentication Flows

Hier liegt der eigentliche Wert, den Keycloak vor AD bringt: MFA für alle Anwendungen, ohne in AD selbst MFA einführen zu müssen.

Der Standard-Browser-Flow von Keycloak 26 enthält bereits einen Browser - Conditional 2FA-Subflow mit OTP. User, die OTP eingerichtet haben, werden automatisch beim Login dazu aufgefordert — ohne dass man am Flow etwas anbauen muss. Was man typischerweise zusätzlich tun möchte:

MFA verpflichtend machen (für alle oder bestimmte Gruppen)

Für alle User:

  • Authentication → Required Actions → Configure OTP auf Default Action setzen. Damit erzwingt Keycloak beim ersten Login die OTP-Einrichtung für jeden neuen User.

Nur für bestimmte AD-Gruppen (z. B. nur für Admins):

  • Browser-Flow duplizieren (z. B. browser-ad-mfa).
  • Im neuen Flow vor den Conditional 2FA-Subflow eine Condition - User Role mit der gewünschten Realm-Rolle setzen.
  • Den OTP Form-Step von Conditional auf Required umschalten.
  • Realm → Authentication → Bindings → Browser Flow auf den neuen Flow umstellen.

WebAuthn / Passkeys statt TOTP

In modernen Setups OTP durch FIDO2/Passkeys ablösen:

  • Authentication → Required Actions → Webauthn Register Passwordless aktivieren und auf Default Action setzen.
  • Im Browser-Flow den OTP-Authenticator durch WebAuthn Authenticator (Two-Factor) oder WebAuthn Passwordless (passwortloser Login) ersetzen.

Was man damit gewinnt

  • MFA für alle Anwendungen, die via Keycloak gehen — Legacy-Apps inklusive (SAML), moderne Apps (OIDC), CLIs (Device Flow).
  • AD-Passwort-Policy bleibt unangetastet, MFA-Logik komplett in Keycloak.
  • Schritt-für-Schritt-Migration zu Passkey-only ohne AD-Änderungen möglich.
  • Self-Service für User über die Account Console (/realms/<name>/account).

Stolpersteine aus der Praxis

  • Self-signed CA: ohne KC_TRUSTSTORE_PATHS (bzw. den passenden Quarkus-Config-Key) wirft Keycloak PKIX path validation failed. AD-CA in einen PKCS12-Truststore packen und referenzieren.
  • Service Account Lockout: wer Bind-Credentials in einer falschen Keycloak-Config testet, kann den Service Account in AD aus Versehen sperren. AD’s Account-Lockout-Policy beachten.
  • Username/Email Case bei Import (siehe Schritt 3) — speziell bei API-Integrationen schmerzhaft.
  • Edit Mode nachträglich ändern: funktioniert nicht zuverlässig. Lieber Provider löschen und neu anlegen.
  • Pagination: false bei großen ADs: AD’s MaxPageSize ist Default 1000. Ohne Pagination werden mehr User schlicht nicht zurückgegeben.
  • Failover-URL nicht gesetzt: ein einzelner DC-Reboot legt den IdP lahm. Immer zwei DCs eintragen.
  • Nested Groups: Preserve Group Inheritance: false setzen. Mit true versucht Keycloak die AD-Gruppen-Hierarchie 1:1 als verschachtelten Group-Tree zu importieren, was an AD’s Gruppen-Topologie scheitert. Mit false werden alle Gruppen flach in Keycloak angelegt und User direkt als Member jeder Gruppe eingetragen — die transitive Auflösung übernimmt dann die rekursive Retrieve Strategy.

Troubleshooting

Bei Problemen die Keycloak-Server-Logs für die LDAP-Kategorie auf TRACE setzen:

quarkus.log.category."org.keycloak.storage.ldap".level=TRACE

Häufige Symptome:

  • Test connection schlägt fehl → Firewall (Port 636) oder Truststore-Problem.
  • Test authentication schlägt fehl → Bind DN falsch oder Service Account gesperrt; mit ldapsearch direkt vom Keycloak-Host gegenprüfen.
  • Sync läuft durch, aber keine User in Keycloak → Users DN falsch oder Search Filter zu restriktiv.
  • Gruppen werden nicht importiert → LDAP Groups DN falsch; Service Account hat keinen Lesezugriff auf die Group-OU; Group Object Classes falsch (in AD ist es group, nicht groupOfNames).
  • User-Status wird ignoriert (in AD deaktivierter User loggt sich ein) → MSAD User Account Control Mapper fehlt oder ist deaktiviert.

Für Connection-Pool-Probleme:

com.sun.jndi.ldap.connect.pool.debug=all

Fazit

Keycloak vor AD ist kein Selbstzweck. Es gibt drei sehr konkrete Hebel:

  1. AD bleibt Source of Truth, ohne dass moderne Apps das wissen müssen — sie sprechen OIDC/SAML mit Keycloak, fertig.
  2. MFA-Schicht für Legacy- und Cloud-Apps gleichermaßen, ohne in AD selbst MFA-Infrastruktur einführen zu müssen.
  3. Saubere Trennung zwischen Authentifizierungs-Layer (Keycloak) und Identity-Lifecycle-Layer (AD/IGA wie Saviynt).

Das eigentliche Setup ist überschaubar — der Wert liegt in der bewussten Wahl von Edit Mode, MSAD-Mapper, Sync-Strategie und MFA-Flow. Wer diese vier Hebel sauber stellt, hat einen IdP, der jahrelang trägt und problemlos in Richtung Passkey-only oder Migration weg von AD wandert.