CodamAIDocs
Themafertig

Token-Tausch (Token-Exchange)

Warum CIAS jedes Benutzer-Token gegen eines für den eigenen Client tauscht, wie lange das Ergebnis gemerkt wird und was bei falsch konfiguriertem Realm passiert.

Ausprägungen
Tausch mit Cache-TrefferTausch ohne CacheToken ohne jti (kein Cache)Realm falsch konfiguriert → 401Keycloak nicht erreichbar → 503

Worum es geht

Ein Token wird immer für einen bestimmten Client ausgestellt. Meldet sich jemand im Hub an, bekommt der BFF ein Token für den Client des Hubs. Darin steht, was der Hub-Client sehen darf, und nicht unbedingt alles, was CIAS braucht: die Rollen des Backend-Clients, die Organisationen, die Attribute für den Mandanten.

Deshalb tauscht CIAS jedes eingehende Token bei Keycloak gegen eines für seinen eigenen Client ein, etwa cias-backend oder cdms-backend. Das Verfahren heißt Token-Exchange und ist ein Standard von OAuth.

Keycloak tauscht nur, wenn das eingehende Token den Client von CIAS in seiner Audience (aud) nennt. Die einzige Ausnahme: Der Client tauscht sein eigenes Token. Genau diese Regel prüft CIAS schon beim Prüfen des Tokens, sodass ein Token für einen fremden Client gar nicht erst bis zum Tausch kommt. Das getauschte Token muss als azp den Client von CIAS nennen, sonst nimmt CIAS es nicht an.

Der Vorteil: Das Frontend muss nicht wissen, welche Rollen und Claims das Backend braucht. Es schickt sein Token, CIAS besorgt sich den Rest.

Der Ablauf

sequenceDiagram
    participant C as Client
    participant S as Filterkette (CIAS)
    participant M as Cache
    participant K as Keycloak
    C->>S: Anfrage mit Token (jti = abc)
    S->>M: gibt es ein getauschtes Token für abc?
    alt Treffer
        M-->>S: getauschtes Token
    else kein Treffer
        S->>K: POST /token<br/>grant_type=token-exchange<br/>subject_token=…
        K-->>S: Token für den CIAS-Client
        S->>M: merken unter abc
    end
    S->>S: Identität aus dem getauschten Token lesen

jti ist die eindeutige ID eines Tokens, ein Claim, den Keycloak in jedes Token schreibt. Sie ist der Schlüssel des Caches.

Die Anfrage an Keycloak

FeldWert
grant_typeurn:ietf:params:oauth:grant-type:token-exchange
client_id, client_secretder Client von CIAS (cias_client, cias_client_secret)
subject_tokendas Token, das der Client geschickt hat
subject_token_typeurn:ietf:params:oauth:token-type:access_token
audiencenur wenn restrict-audience: true, dann der eigene Client

Es ist derselbe Client, dessen Rollen CIAS danach aus resource_access.<client>.roles liest. So passen getauschtes Token und gelesene Rollen immer zusammen.

Die Varianten

Vier Fälle beim Tausch

Wann: Dasselbe Token (gleiche jti) kam vor kurzem schon einmal.

CIAS nimmt das gemerkte getauschte Token. Keycloak wird nicht gefragt.

Ergebnis: Der schnelle Normalfall, denn ein Client schickt dasselbe Token bei vielen Anfragen hintereinander.

Wann: Ein neues Token, zum Beispiel direkt nach einem Refresh.

CIAS tauscht bei Keycloak und merkt sich das Ergebnis. Es bleibt höchstens 5 Minuten im Cache und nie länger als bis 10 Sekunden vor Ablauf des getauschten Tokens.

Ergebnis: Ein Aufruf an Keycloak, danach wieder Treffer.

Wann: Das Token hat keine jti.

Ohne Schlüssel kann CIAS nichts merken. Jede Anfrage mit diesem Token wird bei Keycloak neu getauscht.

Ergebnis: Funktioniert, kostet aber bei jeder Anfrage einen Aufruf an Keycloak.

Wann: Keycloak lehnt den Tausch ab, etwa weil der Tausch am Client nicht eingeschaltet ist.

  1. 1
    CIAS→Keycloak
    bittet um den Tausch
  2. 2
    Keycloak→CIAS
    400 invalid_request: Standard token exchange is not enabled…
  3. 3
    CIAS
    schreibt einen Fehler mit Status und Antwort von Keycloak ins Log
  4. 4
    CIAS
    beendet die Anfrage mit 401 cias.authentication.token-rejected

Ergebnis: Die Anfrage erreicht die Anwendung nicht. Sie läuft nie ohne Identität weiter.

Wann: Keycloak antwortet nicht oder mit einem Serverfehler (5xx).

CIAS schreibt den Grund ins Log und antwortet mit 503 cias.authentication.identity-provider-unavailable. Am Token liegt es nicht.

Ergebnis: Die Anfrage kann später gelingen.

Einstellungen

application.yml
codamai:
  cias:
    token-exchange:
      ttl: 5m                 # CIAS_TOKEN_EXCHANGE_TTL
      expiry-margin: 10s      # CIAS_TOKEN_EXCHANGE_MARGIN
      max-entries: 10000      # CIAS_TOKEN_EXCHANGE_MAX_ENTRIES
      restrict-audience: false
EinstellungStandardBedeutung
ttl5 Minutenwie lange ein getauschtes Token höchstens gemerkt wird
expiry-margin10 SekundenAbstand zum Ablauf des getauschten Tokens, ab dem es nicht mehr aus dem Cache kommt
max-entries10 000Obergrenze des Caches. Ist er voll, fliegen erst abgelaufene Einträge, sonst wird er geleert
restrict-audiencefalseob audience mitgeschickt wird

Weiter

Quellen im Code und in der Wissensdatenbank
  • CIAS/cias-authentication – TokenExchangeService (exchangeToken, Cache, Formularfelder, Fehlerbehandlung), TokenExchangeProperties, TokenCacheItem, TokenParser
  • CIAS/cias-authentication/docs/adr – ADR-030, ADR-049
  • CIAS/cias-authentication – CiasTokenValidation (exchanged), IdentityProviderUnavailableException, RequestAdmission
  • CIAS/cias-runtime – application.yml (codamai.cias.token-exchange), deploy/keycloak/import/codamai-realm.json (standard.token.exchange.enabled, self-audience)
Suchen