Zum Inhalt springen

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.


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

Alles, was die Fachschichten über den Aufrufer wissen, steht hier:

FeldInhalt
requestId, startTimetechnische Kennung, Startzeitpunkt
userIdsub aus dem Token
userNamename aus dem Token
userTenanteffektiver Mandant
allowedTenantsMandanten, die dieser Benutzer erreichen darf (aus dem Token)
userIdSwitchRequest, userTenantSwitchRequestWünsche aus den Headern user bzw. tenant
effectiveUserRealmRolesRealm-Rollen (administrativ)
effectiveUserRolesClient-Rollen des cias_client (fachlich)
effectiveUserGroupsGruppen
effectiveUserAttributesalle Nicht-Protokoll-Claims als String[]
strictModeVoreinstellung aus STRICT_MODE, Standard true
userIP, userClientAgentfü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.

Quelle im TokenZiel
subuserId
nameuserName
realm_access.roleseffectiveUserRealmRoles
resource_access.<cias_client>.roleseffectiveUserRoles
groupseffectiveUserGroups
Attribut tenantuserTenant
Attribut allowedTenantsallowedTenants (plus Heimatmandant)
alle übrigen ClaimseffectiveUserAttributes

Zwei Feinheiten, die im Code eigens begründet sind:

  1. realm_access wird ausdrücklich verschachtelt gelesen. Ein getClaimAsStringList("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).
  2. 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 mit ClassCastException um.

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.

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.

HeaderWirkungVoraussetzung
tenantanderer MandantRealm-Rolle allowed-tenant-context-switch und Ziel in allowedTenants
userandere Benutzer-IDRealm-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.

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.

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.

ClientZweck
<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-ClientClient-Credentials für den Modell-Abruf beim Build

Die Client-IDs bekommen Sie vom Betreiber; dynamische Client-Registrierung (RFC 7591) ist bewusst gesperrt.

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.