Authentifizierung
Kurzantwort: Jeder Request läuft durch den JwtSessionFilter aus
cias-authentication. Er baut einen RequestContext auf, prüft das
Bearer-Token gegen Keycloak, tauscht es ggf. aus und übernimmt Benutzer-ID,
Mandant, Rollen, Gruppen und Attribute in einen ThreadLocal-Kontext, den alle
weiteren Schichten lesen. Kontextwechsel per Header wirken nur mit passender
Realm-Rolle.
Der Ablauf
Abschnitt betitelt „Der Ablauf“sequenceDiagram
autonumber
participant C as Client
participant F as JwtSessionFilter
participant TP as TokenParser
participant D as JwtDecoder
participant TX as TokenExchangeService
participant CS as ContextSwitch
participant A as Anwendung
C->>F: Request mit Authorization: Bearer …
F->>F: IP (X-Forwarded-For / X-Real-IP / Remote),<br/>User-Agent, requestId, startTime
F->>F: RequestContext anlegen und in ThreadLocal setzen
alt Bearer-Token vorhanden
F->>TP: tokenParser(token)
TP->>D: decode – Signatur, Issuer, Ablauf
TP->>TX: exchangeToken(jwt, sub)
TP->>TP: Claims auslesen
TP->>CS: contextSwitch()
CS->>CS: Tenant-/User-Wechsel prüfen und ggf. anwenden
else kein Token
Note over F: Kontext bleibt anonym (öffentlicher Zugriff)
end
F->>A: Kette fortsetzen
Der RequestContext
Abschnitt betitelt „Der RequestContext“Alles, was die Fachschichten über den Aufrufer wissen, steht hier:
| Feld | Inhalt |
|---|---|
requestId, startTime | technische Kennung, Startzeitpunkt |
userId | sub aus dem Token |
userName | name aus dem Token |
userTenant | effektiver Mandant |
allowedTenants | Mandanten, die dieser Benutzer erreichen darf (aus dem Token) |
userIdSwitchRequest, userTenantSwitchRequest | Wünsche aus den Headern user bzw. tenant |
effectiveUserRealmRoles | Realm-Rollen (administrativ) |
effectiveUserRoles | Client-Rollen des cias_client (fachlich) |
effectiveUserGroups | Gruppen |
effectiveUserAttributes | alle Nicht-Protokoll-Claims als String[] |
strictMode | Voreinstellung aus STRICT_MODE, Standard true |
userIP, userClientAgent | für das Auditing |
Der Kontext liegt in einem ThreadLocal (RequestContextHolder). Wer in eigene
Threads verzweigt, muss ihn ausdrücklich propagieren und danach wieder aufräumen
– sonst leckt ein alter Kontext in die nächste Aufgabe desselben (virtuellen)
Threads.
Claim-Auswertung
Abschnitt betitelt „Claim-Auswertung“| Quelle im Token | Ziel |
|---|---|
sub | userId |
name | userName |
realm_access.roles | effectiveUserRealmRoles |
resource_access.<cias_client>.roles | effectiveUserRoles |
groups | effectiveUserGroups |
Attribut tenant | userTenant |
Attribut allowedTenants | allowedTenants (plus Heimatmandant) |
| alle übrigen Claims | effectiveUserAttributes |
Zwei Feinheiten, die im Code eigens begründet sind:
realm_accesswird ausdrücklich verschachtelt gelesen. EingetClaimAsStringList("realm_access.roles")würde nach einem Claim mit genau diesem Namen suchen, nichts finden und eine leere Liste liefern – jede Realm-Rollenprüfung schlüge dann still fehl (fail-closed, aber niemand hätte je eine Realm-Rolle).- Attributwerte werden zu
String[]normalisiert. Die Rohwerte durchzureichen scheint zu funktionieren (der ungeprüfte Cast gelingt durch Type Erasure), fällt aber beim ersten Array-Zugriff mitClassCastExceptionum.
Als Protokoll-Claims gelten die OIDC-Standard-Claims, die OIDC-Profilfelder
und Keycloak-spezifische Claims wie realm_access, resource_access, groups.
Alles andere behandelt CDMS als Benutzerattribut.
Token-Exchange
Abschnitt betitelt „Token-Exchange“Nach dem Decodieren tauscht TokenExchangeService das Token
(exchangeToken(jwt, sub)). Damit arbeitet die Anwendung mit einem Token, das
die für sie relevanten Rollen und Attribute trägt – auch wenn der Client mit
einem anderen Audience-Token kommt.
Kontextwechsel
Abschnitt betitelt „Kontextwechsel“| Header | Wirkung | Voraussetzung |
|---|---|---|
tenant | anderer Mandant | Realm-Rolle allowed-tenant-context-switch und Ziel in allowedTenants |
user | andere Benutzer-ID | Realm-Rolle allowed-user-context-switch |
Beides ist fail-closed: fehlt die Rolle oder passt das Ziel nicht, bleibt der Kontext unverändert – der Wechsel findet still nicht statt, es gibt keinen Teilerfolg. Der Mandantenwechsel hat damit zwei Tore: „darf überhaupt wechseln” und „darf dorthin wechseln”. Die Persistenz prüft zusätzlich defensiv nach.
Ohne Token
Abschnitt betitelt „Ohne Token“Fehlt der Authorization-Header, wird kein Token geparst – der Kontext bleibt
anonym. Das ist der Weg für öffentliche Endpunkte. Fachlich wirkt danach die
Rollenprüfung: Ein Modell mit gesetzter Leserolle ist ohne Rollen nicht lesbar.
Fehlerfälle
Abschnitt betitelt „Fehlerfälle“Ein ungültiges Token führt heute zu einer JwtException, die im TokenParser
protokolliert wird; der Request läuft ohne Benutzerkontext weiter und scheitert
anschließend an der Rechteprüfung. Für einen sauberen 401 ist im Code eine
TODO-Markierung gesetzt.
Keycloak-Clients
Abschnitt betitelt „Keycloak-Clients“| Client | Zweck |
|---|---|
<cias_client> | Ihre Anwendung selbst; ihre resource_access-Rollen sind die Fachrollen |
| MCP-Client | öffentlicher Client mit PKCE für den Zugriff auf den Hub, siehe MCP-Server |
| Generator-Client | Client-Credentials für den Modell-Abruf beim Build |
Die Client-IDs bekommen Sie vom Betreiber; dynamische Client-Registrierung (RFC 7591) ist bewusst gesperrt.
Profil abfragen
Abschnitt betitelt „Profil abfragen“GET /cias/fetch liefert das Profil des aktuellen Requests – Rollen und
Attribute, wie CDMS sie sieht. Nützlich für Frontends und zur Fehlersuche bei
Rechteproblemen.