Worum es geht
CIAS schreibt vieles in den Identity Provider (kurz IdP), also in Keycloak: Konten, Organisationen, Rollen, Gruppen, Attribute. Trotzdem kennt keines der Fachmodule Keycloak. Die Registrierung weiß nicht, welche URL Keycloak hat, und die Rollenvergabe weiß nicht, wie Keycloak eine Rolle speichert.
Das liegt an einer Trennung in zwei Teile:
- Ein Port ist eine Schnittstelle in Java. Sie sagt nur, was gebraucht wird: „lege ein Konto an“, „vergib diese Rolle in diesem Mandanten“.
- Ein Adapter ist eine Klasse, die diese Schnittstelle für ein bestimmtes System erfüllt. Er weiß, wie das in Keycloak geht: welcher HTTP-Aufruf, welche Felder, welche Antwort.
Das Bild
flowchart LR
subgraph Fachmodule
R["cias-registration"]
U["cias-user"]
A["cias-authorization"]
end
subgraph P["cias-iam-api: Ports"]
P1["Konten"]
P2["Organisationen"]
P3["Rollen"]
P4["Gruppen"]
P5["Attribute"]
end
R --> P
U --> P
A --> P
P --> KA["cias-iam-keycloak<br/>Keycloak-Adapter"]
P --> MA["cias-iam-memory<br/>Speicher-Adapter"]
KA -- "Admin-REST-API" --> K["Keycloak"]
MA --> M[("Speicher<br/>im Prozess")]
Lies es von links nach rechts: Die Fachmodule kennen nur die Mitte. Rechts steht genau ein Adapter, je nach Einstellung. Die Pfeile zeigen nur in eine Richtung. Kein Fachmodul greift an der Mitte vorbei auf Keycloak zu. Das prüft in jedem Fachmodul ein Architekturtest: ein Test, der beim Bauen scheitert, sobald eine Klasse ein Keycloak-Paket importiert.
Die Ports
Es gibt elf Ports. Jeder deckt eine Fähigkeit ab, nicht ein ganzes System. Ein Modul, das nur Adressen nachschlägt, hängt nicht an der Rollenverwaltung.
| Port | Was er kann | Wer ihn nutzt |
|---|---|---|
IdentityProvisioningPort | Konto gesperrt anlegen, Adresse als bestätigt markieren, freischalten, sperren, „Passwort setzen“ verlangen, einen Passwort-Link holen, beim Aufräumen löschen | Registrierung, Benutzer |
IdentityLookupPort | ein Konto über die E-Mail-Adresse oder die ID finden | Registrierung, Benutzer |
IdentityDirectoryPort | alle Konten seitenweise lesen | Benutzer (Import) |
IdentityAttributePort | ein Attribut an einem Konto setzen oder entfernen | Registrierung, Benutzer |
OrganizationManagementPort | Organisation anlegen, über ihren Alias finden, beim Aufräumen löschen | Registrierung |
MembershipManagementPort | Mitgliedschaft in einer Organisation geben, nehmen, lesen | Registrierung, Benutzer |
RoleManagementPort | Realm-Rollen: prüfen, anlegen, global oder in einer Organisation vergeben, entziehen, lesen | Rollen, Registrierung |
ClientRoleManagementPort | dasselbe für Rollen eines Clients, also Modulrollen | Rollen, Registrierung |
GroupManagementPort | die Kopie einer CIAS-Gruppe in Keycloak pflegen | Gruppen |
UserProfileManagementPort | welche Attribute ein Konto überhaupt tragen darf | Rollen und Attribute (Abgleich) |
ClaimMappingPort | ein Attribut in die Tokens eines Clients bringen | Rollen und Attribute (Abgleich) |
Drei Begriffe aus der Tabelle:
- Eine Realm-Rolle gilt im ganzen Realm und damit in allen Modulen, etwa
platform-admin. Eine Client-Rolle gehört zu einem Client und bedeutet nur dort etwas, etwa eine Rolle von CDMS. Siehe Realm-Rolle, Client-Rolle, Organisationsrolle. - Eine Organisation ist in Keycloak das Abbild eines dynamischen Mandanten. Siehe Statische und dynamische Mandanten.
- Das User Profile ist in Keycloak ein Dokument je Realm, das festlegt, welche Attribute ein Konto haben darf.
Die Regeln hinter den Ports
Die Ports sind mit Absicht so geschnitten. Jede Regel verhindert einen bestimmten Fehler.
- Kein Keycloak-Typ in einem Port. Keine Signatur und keine Fehlerklasse nennt Keycloak. Der Port kennt nur eigene, kleine Datensätze wie
NewIdentityoderIamIdentity. - Kein Passwort. Es gibt „Passwort setzen verlangen“, aber kein „Passwort setzen“. CIAS nimmt nie ein Passwort entgegen.
- Kein Benutzername. Die E-Mail-Adresse ist die Anmeldung. Ein Konto je Adresse, beliebig viele Mandanten.
- Global und „in einer Organisation“ sind getrennte Methoden.
assignvergibt plattformweit,assignInOrganizationnur in einem Mandanten. Wären beide eine Methode mit einem optionalen Mandanten, könnte ein Mandanten-Administrator sich aus Versehen plattformweite Rechte geben. - Realm-Rollen und Client-Rollen sind getrennte Ports. Aus demselben Grund: Über den Port für Client-Rollen kann man keine Realm-Rolle erreichen.
- Jeder Aufruf darf zweimal kommen. Eine Rolle vergeben, die schon da ist, ändert nichts. Ein Konto freischalten, das schon frei ist, auch nicht. Zwischen CIAS und Keycloak gibt es keine gemeinsame Transaktion. Geht eine Antwort verloren, ist Wiederholen der einzige Weg, und der darf nichts kaputt machen.
- Eine neue Fähigkeit ist ein neuer Port. Keine Sammelschnittstelle mit dreißig Methoden.
- Ein Attribut braucht drei Schritte, jeder in seinem Port. Das Profil erlaubt es (
UserProfileManagementPort), ein Konto bekommt einen Wert (IdentityAttributePortoder beim Anlegen), und der Wert kommt ins Token (ClaimMappingPort). Siehe Der Weg ins Token.
Verweise statt Schlüssel
Keycloak vergibt eigene IDs. CIAS hält sie in kleinen Hüllen fest: ExternalUserId, ExternalOrganizationId, ExternalGroupId, ExternalRoleId. Das sind nur Verweise: „dieses Ding liegt dort im IdP“.
Die fachliche Identität bleibt in CIAS:
| Objekt | fachlich bestimmt durch | in Keycloak wiedergefunden über |
|---|---|---|
| Person | ihre CIAS-ID | ExternalUserId |
| Mandant | den Mandantenschlüssel | Alias der Organisation = Mandantenschlüssel |
| Gruppe | den Gruppenschlüssel | Name der Gruppe = Gruppenschlüssel |
| Rolle | ihren Namen | Name der Rolle |
Wer den IdP wechselt, ändert also Verweise, nicht Daten.
Wenn Keycloak etwas nicht tut
Jeder Port meldet Probleme mit einer von vier Fehlerarten. Die Fachmodule reagieren auf die Art, nicht auf einen HTTP-Status.
Wann: Keycloak antwortet nicht, antwortet mit einem Serverfehler, oder CIAS bekommt kein Anmelde-Token für sich selbst.
IamUnavailableException. Die einzige Art, die man wiederholen darf. Ob der Aufruf schon gewirkt hat, weiß niemand, und deshalb ist jeder Aufruf so gebaut, dass er zweimal kommen darf.
Ergebnis: Aufrufer wartet oder versucht es später erneut
Wann: Es gibt schon ein Konto mit dieser Adresse, eine Organisation mit diesem Alias oder eine Gruppe mit diesem Namen.
IamConflictException. Keycloak setzt Eindeutigkeit durch. Diese Meldung darf nie ungefiltert bei einer nicht angemeldeten Person ankommen, sonst verrät ein öffentlicher Endpunkt, welche Adressen es gibt.
Ergebnis: Aufrufer entscheidet, oft: so weitermachen, als hätte die Suche das Objekt gefunden
Wann: CIAS verweist auf ein Konto, eine Rolle, einen Client oder eine Gruppe, die es in Keycloak nicht (mehr) gibt.
IamNotFoundException. Meist ein Zeichen, dass CIAS und Keycloak auseinandergelaufen sind, etwa weil jemand in der Keycloak-Konsole etwas gelöscht hat.
Ergebnis: Fehler, oft ein Fall für einen Abgleich
Wann: Keycloak lehnt aus einem anderen Grund ab, oder CIAS soll etwas ändern, das nicht CIAS gehört.
IdentityProvisioningException. Der ehrliche Sammeltopf: nicht wiederholen, sondern festhalten und stehen bleiben.
Ergebnis: Fehler, der Vorgang bleibt in einem erkennbaren Zustand
Suchen melden „nicht gefunden“ nicht als Fehler, sondern als leeres Ergebnis. „Unter dieser Adresse gibt es kein Konto“ ist auf einem öffentlichen Endpunkt die normale Antwort.
Welcher Adapter läuft
Zwei Dinge müssen zusammenkommen: Das Jar des Adapters liegt im Klassenpfad, und die Einstellung nennt ihn.
Wann: codamai.cias.iam-provider: keycloak, und cias-iam-keycloak liegt im Klassenpfad.
CIAS spricht über die Admin-REST-API mit Keycloak. Wo Keycloak steht und mit welchem Dienstkonto CIAS sich anmeldet, steht unter codamai.cias.keycloak.*. Siehe Der Keycloak-Adapter.
Ergebnis: Normalfall jeder Installation
Wann: codamai.cias.iam-provider: memory, und cias-iam-memory liegt im Klassenpfad.
Alle Ports arbeiten auf einer Ablage im Arbeitsspeicher des Prozesses. Nach einem Neustart ist alles weg. Siehe Der Speicher-Adapter für Tests und Entwicklung.
Ergebnis: Tests und lokale Entwicklung
Wann: Die Einstellung fehlt, oder sie nennt einen Adapter, dessen Jar fehlt.
Es gibt keine Ports. Die Anwendung startet nicht und nennt den fehlenden Port. Das ist Absicht: Ein CIAS ohne IdP würde Registrierungen annehmen, die es nie ausführen kann.
Ergebnis: kein Start
Wert von iam-provider | Jar im Klassenpfad? | Ergebnis |
|---|---|---|
keycloak | ja | Keycloak-Adapter |
memory | ja | Speicher-Adapter |
keycloak oder memory | nein | keine Ports, kein Start |
| leer | – | keine Ports, kein Start |
Liegen beide Jars im Klassenpfad, läuft trotzdem nur der eine, den die Einstellung nennt. Das eigenständige CIAS liefert beide mit: Im Profil local steht memory, sonst keycloak.
Ein zweiter Provider ist ein zweites Artefakt
Soll CIAS einmal mit einem anderen IdP arbeiten, ändert niemand die Fachmodule. Stattdessen entsteht ein neues Modul, etwa cias-iam-<name>:
-
1Entwicklerlegt ein eigenes Modul an, das nur
cias-iam-apiund die Bibliothek des neuen IdP kennt -
2Entwicklersetzt die Ports um, die der neue IdP beherrscht, und übersetzt seine Fehler in die vier Fehlerarten
-
3Buildlässt dieselben Vertragstests laufen wie für Keycloak und den Speicher
-
4Entwicklergibt dem Adapter einen Namen für
codamai.cias.iam-providerund verdrahtet ihnErgebnis: Fachmodule unverändert, nur die Einstellung ändert sich
Warum ein eigenes Artefakt und nicht ein Schalter im Keycloak-Adapter? Jeder Adapter bringt die Bibliotheken seines IdP mit. Stünden zwei IdPs in einem Modul, hinge jede Installation an beiden. Und die Vertragstests prüfen genau einen Adapter auf einmal.
Nicht jeder IdP kann alles. Organisationen, Rollen in Organisationen oder Rollen an Gruppen sind Fähigkeiten von Keycloak. Ein IdP ohne Organisationen kann keine dynamischen Mandanten tragen.