Keycloak richtig aufsetzen: Active Directory, MFA und eine Client-Factory für Teams

Inhalt

Worum geht’s

In den meisten Unternehmen ist Active Directory die Source of Truth für Identitäten, moderne Anwendungen erwarten aber OpenID Connect oder SAML. Keycloak schließt diese Lücke: Es föderiert das bestehende AD per LDAPS, wird zum zentralen Identity Provider für alle Anwendungen und legt MFA darüber, ohne dass AD umgebaut werden muss.

Spannend wird es, wenn nicht drei, sondern dreihundert Anwendungen dazukommen. Dann entscheidet nicht mehr die LDAP-Konfiguration über den Erfolg, sondern die Frage, wie Teams schnell und sicher an ihre OIDC-Clients kommen, ohne für jeden Client ein Ticket beim IAM-Team zu öffnen. Dieser Leitfaden zeigt beides: die AD-Anbindung, wie ich sie in Produktion aufbaue, und eine Client-Factory, über die Teams ihre Clients selbst verwalten. Alle Pfade beziehen sich auf Keycloak 26.

Das Wichtigste in Kürze

  • Ein zentraler Realm mit AD-Anbindung, MFA und Rollenmodell, einmal sauber gebaut und von allen Anwendungen genutzt.
  • Ausfallsicher: LDAPS gegen mindestens zwei Domain Controller, Nested Groups löst der DC selbst auf.
  • MFA ohne Umbau in AD: OTP, Security Keys oder Passkeys, gesteuert über AD-Gruppen.
  • Self-Service statt Tickets: Teams beschreiben ihre OIDC-Clients in Git, eine Pipeline legt sie per Terraform an.
  • Leitplanken im Realm: Client Policies erzwingen PKCE, erlaubte Redirect-Domains und schlanke Tokens, egal wer den Client anlegt.

Architektur auf einen Blick

Architektur: Web-Apps, Legacy-Apps und APIs sprechen OIDC, SAML oder OAuth 2.0 mit Keycloak 26. Keycloak bindet Active Directory mit zwei Domain Controllern per LDAPS auf Port 636 an, ein optionales IGA-System wie Saviynt oder OpenText pflegt den Lifecycle in AD.
Keycloak als Identity Provider vor Active Directory. Anwendungen sehen nur OIDC und SAML, AD nur LDAPS.
  • Anwendungen sprechen ausschließlich OIDC oder SAML mit Keycloak. LDAP und AD bleiben für sie unsichtbar.
  • Keycloak authentifiziert, führt die Authentication Flows inklusive MFA aus und stellt Tokens mit den passenden Claims aus.
  • Active Directory bleibt das User-Verzeichnis. Passwörter prüft Keycloak per LDAP-Bind direkt am DC und importiert sie nie. Ein IGA-System wie Saviynt, SailPoint oder OpenText Identity Manager kann den Lifecycle der Identitäten verantworten.
Sequenzdiagramm: Browser ruft die Anwendung auf, die Anwendung leitet zu Keycloak weiter, der User gibt Username und Passwort ein, Keycloak sucht den User als Service Account und prüft das Passwort per Bind am DC, danach folgen MFA und die Ausstellung der Tokens.
Ein Login im Detail: Schritt 4 und 5 laufen gegen AD, alles andere passiert in Keycloak.

Active Directory anbinden: fünf Entscheidungen, die zählen

Der LDAP Provider ist in einer Stunde konfiguriert. Ob er jahrelang stabil läuft, entscheiden diese fünf Punkte.

1. LDAPS gegen mindestens zwei Domain Controller

User federation → Add LDAP providers

Vendor:               Active Directory
Connection URL:       ldaps://dc1.example.com:636 ldaps://dc2.example.com:636
Use Truststore SPI:   Always
Connection pooling:   On
Connection timeout:   3000
Bind DN:              CN=svc-keycloak,OU=Service Accounts,DC=example,DC=com
Bind credentials:     ${vault.ldap_credential}
  • DCs nur durch Leerzeichen trennen. Keycloak probiert sie von links nach rechts, der Connection timeout gilt pro nicht erreichbarem DC. Mit 3000 ms bleibt das Failover zügig.
  • Truststore: Die AD-Root-CA als PEM nach conf/truststores legen oder über KC_TRUSTSTORE_PATHS einbinden. Der FQDN aus der Connection URL muss im SAN der DC-Zertifikate stehen.
  • Vorher testen: Was mit ldapsearch vom Keycloak-Host funktioniert, funktioniert auch in Keycloak.
LDAPTLS_CACERT=/etc/pki/ad-root-ca.pem ldapsearch -LLL -H ldaps://dc1.example.com:636 \
  -D 'CN=svc-keycloak,OU=Service Accounts,DC=example,DC=com' -W \
  -b 'OU=Users,DC=example,DC=com' '(sAMAccountName=jdoe)' mail memberOf

2. Vendor, Edit Mode und Username vor dem ersten Speichern

Vendor Active Directory ist der wichtigste Klick im ganzen Setup. Keycloak setzt damit die AD-Defaults (objectGUID, Object Classes) und legt die passenden Mapper an, allen voran MSAD account controls. Weil die Mapper aus Vendor, Edit Mode und Username-Attribut entstehen, gehören diese drei Werte ins erste Speichern:

Edit ModeWannPasswortänderung
READ_ONLYEin IGA oder ein bestehendes Self-Service-Portal verantwortet den Lifecycle.außerhalb von Keycloak
WRITABLEDie Keycloak Account Console wird zum Self-Service.per LDAPS direkt nach AD

Als Username LDAP attribute passt sAMAccountName für Logins wie jdoe, userPrincipalName für Logins im E-Mail-Format.

3. Den Scope per LDAP-Filter schneiden

Statt das halbe Verzeichnis zu importieren, holt ein Filter genau die User, die Zugriff haben sollen, inklusive verschachtelter Gruppen:

(&(objectCategory=person)(memberOf:1.2.840.113556.1.4.1941:=CN=APP-Keycloak-Users,OU=Groups,DC=example,DC=com))

1.2.840.113556.1.4.1941 ist LDAP_MATCHING_RULE_IN_CHAIN: Der DC löst die transitive Mitgliedschaft serverseitig auf. Dazu Search scope: Subtree und Pagination: On, denn AD liefert pro Anfrage maximal 1000 Einträge.

4. Sync und Cache

Periodic changed users sync:   3600       # stündlich
Periodic full sync:            86400      # nachts
Cache policy:                  MAX_LIFESPAN
Max lifespan:                  180000     # 3 Minuten

Der nächtliche Full Sync hat einen konkreten Grund: Kommt ein User in AD in eine Gruppe, ändert sich das Gruppenobjekt, nicht das User-Objekt. Wer über den memberOf-Filter neu in den Scope fällt, erscheint deshalb erst beim Full Sync oder beim ersten Login. Der Cache mit drei Minuten Lebensdauer hält die Last auf den DCs klein, und Änderungen sind trotzdem planbar schnell sichtbar.

5. Deaktivierte Accounts zuverlässig sperren

Der Mapper MSAD account controls übersetzt userAccountControl in Keycloak-Verhalten: Ein in AD deaktivierter Account ist in Keycloak disabled. Mit Always Read Enabled Value From LDAP: On gilt das auch für Logins ohne Passwort-Bind, etwa mit Passkeys. In WRITABLE führt ein abgelaufenes AD-Passwort direkt zur Required Action Update Password.

Gruppen und Rollen aus AD

Ein group-ldap-mapper importiert die relevanten AD-Gruppen nach /ad:

LDAP Groups DN:                 OU=Groups,DC=example,DC=com
LDAP Filter:                    (cn=APP-*)
Preserve Group Inheritance:     Off
User Groups Retrieve Strategy:  LOAD_GROUPS_BY_MEMBER_ATTRIBUTE_RECURSIVELY
Groups Path:                    /ad
Mode:                           READ_ONLY

Die rekursive Strategie löst Nested Groups direkt auf dem DC auf. Preserve Group Inheritance: Off verhindert, dass Gruppen mit mehreren Eltern den Sync stoppen, und der LDAP Filter hält tausende irrelevante Gruppen draußen.

Daraus wird ein sauberes Rollenmodell: Die AD-Gruppe APP-Admins erscheint als /ad/APP-Admins, an der Gruppe hängt die Rolle app-admin. Die Mitgliedschaft wird in AD und den bestehenden Genehmigungsprozessen gepflegt, die Berechtigung in Keycloak. Im Token landen Rollen in realm_access.roles, Gruppen über einen Client Scope groups mit Group Membership Mapper:

{
  "preferred_username": "jdoe",
  "groups": ["APP-Admins", "APP-Users"],
  "realm_access": { "roles": ["app-admin"] }
}

MFA, gesteuert über AD-Gruppen

Hier liegt der größte Mehrwert von Keycloak vor AD: eine MFA-Schicht für alle Anwendungen, ob OIDC oder SAML, ohne MFA-Infrastruktur in AD.

  1. Authentication → browser → Action → Duplicate als browser-mfa.
  2. Im forms-Subflow einen Subflow MFA enforcement (Conditional) anlegen: Condition - User Role auf die Rolle mfa-required, Condition - sub-flow executed auf den 2FA-Subflow mit not-executed, darunter OTP Form als Required. Die zweite Condition verhindert, dass User mit bereits eingerichtetem OTP doppelt gefragt werden.
  3. Die AD-Gruppe APP-MFA-Required per Group Mapper importieren und an mfa-required binden.
  4. Action → Bind flow → Browser flow.

Damit läuft der MFA-Rollout über die AD-Gruppenpflege, Abteilung für Abteilung, ohne einen Klick in Keycloak. Passkeys (Webauthn Register Passwordless) und Recovery Codes als Fallback ergänzt du im selben Flow. Die Brute Force Detection stellst du unter die Lockout-Schwelle von AD, damit Password-Spraying über die Login-Seite keine AD-Accounts sperren kann.

Client-Factory: Teams verwalten ihre OIDC-Clients selbst

Ein zentraler Realm entfaltet seinen Wert erst, wenn neue Anwendungen schnell andocken können. Der klassische Weg ist ein Ticket an das IAM-Team, eine manuelle Anlage in der Admin Console und ein Client Secret per Chat. Das skaliert nicht und ist das Gegenteil von sicher.

Die Lösung ist eine Client-Factory. Der Realm mit AD-Anbindung, MFA und Rollenmodell wird einmal zentral gebaut, und Teams verwalten ihre OIDC-Clients darin selbst, per Pull Request und innerhalb fester Leitplanken.

Client-Factory: Ein Team beschreibt seinen Client in client.yaml, nach Review im Pull Request legt eine Pipeline per terraform apply den Client im zentralen Realm an. Das Client Secret geht in den Secret Store, aus dem die App des Teams es liest. Im Realm wirken Client Policies und Admin Permissions.
Die Client-Factory: Self-Service für Teams, Leitplanken und Audit-Trail für das IAM-Team.

Teams in der Admin Console: nur die eigenen Clients

Mit den Fine-Grained Admin Permissions v2 (ab Keycloak 26.2, Realm settings → Admin Permissions) sehen und pflegen Team-Admins in der Admin Console genau ihre eigenen Clients. Auch das kommt aus der Factory: Die Ressource keycloak_openid_client_admin_permissions vergibt pro Team die Scopes view und manage auf dessen Clients, gebunden an die AD-Gruppe des Teams.

Auch die AD-Anbindung als Code

Was für die Clients gilt, gilt für den Realm selbst: Der Terraform Provider keycloak/keycloak beschreibt die komplette AD-Anbindung deklarativ. Drift zeigt terraform plan sofort an.

resource "keycloak_ldap_user_federation" "ad" {
  name           = "active-directory"
  realm_id       = data.keycloak_realm.corp.id
  vendor         = "AD"
  edit_mode      = "READ_ONLY"
  import_enabled = true

  connection_url     = "ldaps://dc1.example.com:636 ldaps://dc2.example.com:636"
  use_truststore_spi = "ALWAYS"
  connection_timeout = "3s"

  bind_dn                    = "CN=svc-keycloak,OU=Service Accounts,DC=example,DC=com"
  bind_credential_wo         = var.ad_bind_password
  bind_credential_wo_version = 1

  users_dn                  = "OU=Users,DC=example,DC=com"
  username_ldap_attribute   = "sAMAccountName"
  uuid_ldap_attribute       = "objectGUID"
  user_object_classes       = ["person", "organizationalPerson", "user"]
  custom_user_search_filter = "(&(objectCategory=person)(memberOf:1.2.840.113556.1.4.1941:=CN=APP-Keycloak-Users,OU=Groups,DC=example,DC=com))"
  search_scope              = "SUBTREE"
  pagination                = true

  full_sync_period    = 86400
  changed_sync_period = 3600

  cache {
    policy       = "MAX_LIFESPAN"
    max_lifespan = "3m"
  }
}

bind_credential_wo ist ein Write-only Argument (ab Terraform 1.11): Das Bind-Passwort landet nie im State. vendor = "AD" erzeugt dieselben Default-Mapper wie in der UI, inklusive MSAD account controls.

Troubleshooting: die fünf häufigsten Fehler

SymptomUrsacheLösung
PKIX path building failedAD-CA fehlt im TruststoreCA nach conf/truststores oder KC_TRUSTSTORE_PATHS, Neustart
No subject alternative DNS name matching …Connection URL nutzt IP oder AliasFQDN aus dem DC-Zertifikat verwenden
error code 49 … data 52eBind DN oder Passwort falschmit ldapsearch gegenprüfen
Sync liefert maximal 1000 UserPagination ist ausPagination: On
Nested Groups fehlennicht-rekursive Strategie im Group MapperLOAD_GROUPS_BY_MEMBER_ATTRIBUTE_RECURSIVELY

Für tiefere Analysen protokolliert --log-level="INFO,org.keycloak.storage.ldap:trace" jede LDAP-Query.

Checkliste für die Produktion

  • LDAPS mit mindestens zwei DCs, AD-CA im Truststore, Bind Credential im Vault
  • Vendor, Edit Mode und Username-Attribut beim ersten Speichern gesetzt
  • User-Scope per LDAP-Filter, Pagination: On, stündlicher und nächtlicher Sync
  • Group Mapper rekursiv, mit Filter und Groups Path, Rollen an Gruppen gebunden
  • MFA-Flow gebunden, Recovery Codes aktiv, Brute Force unter der AD-Lockout-Schwelle
  • Client-Factory mit Pull-Request-Review, Leitplanken per Client Policies und Secrets direkt im Secret Store
  • Komplette Konfiguration per Terraform in Git, lokaler Notfall-Admin unabhängig von AD

Fazit

Keycloak vor Active Directory liefert in kurzer Zeit, was AD allein nie kann: OIDC und SAML für alle Anwendungen, MFA ohne Umbau und ein Rollenmodell, das auf den bestehenden AD-Prozessen aufsetzt. Mit der Client-Factory wird daraus eine Plattform: Teams docken in Minuten an, das IAM-Team behält die Leitplanken, und jede Änderung ist im Git nachvollziehbar.


Du planst eine Keycloak-Einführung oder willst eine bestehende Identity-Plattform auf Self-Service umstellen? Schreib mir: david@picillo.de