PROCUREX · Statische HTML-Dokumentation

Keycloak — Realm procurex

Aus keycloak\README.md konvertiert – vollständig lokal lesbar.

Keycloak — Realm procurex

PRX-19 (Umsetzung): echtes OIDC-Login statt Mock-Rollenumschalter. Läuft im geteilten

enterprise-infrastructure-Keycloak (enterprise-infrastructure --profile postgres --profile keycloak up -d keycloak).

Dieses Dokument ist bewusst ausführlich, weil Keycloak im Team bisher wenig bekannt ist —

der erste Abschnitt erklärt die Grundkonzepte, danach folgt die PROCUREX-spezifische

Konfiguration.

Was ist Keycloak überhaupt?

Keycloak ist ein Identity- und Access-Management-Server (IAM). Statt dass jede

Anwendung (Frontend, Backend) selbst Passwörter speichert und ein eigenes Login-Formular

baut, übernimmt Keycloak das zentral für alle angeschlossenen Anwendungen. Es implementiert

den offenen Standard OpenID Connect (OIDC), der wiederum auf OAuth 2.0 aufbaut.

Vorteil gegenüber Eigenbau: Login, Passwort-Reset, Session-Verwaltung, Multi-Faktor-Auth

usw. sind einmal zentral gelöst statt in jeder Anwendung neu — und Anwendungen müssen

niemals selbst ein Passwort sehen oder speichern (siehe Grant-Types unten).

Kernbegriffe

Begriff Bedeutung
Realm Ein isolierter Mandant: eigene Nutzer, Rollen und Clients, komplett getrennt von anderen Realms auf demselben Server. PROCUREX nutzt den Realm procurex, getrennt vom Keycloak-eigenen master-Realm (nur für die Server-Administration selbst).
Client Eine Anwendung, die sich gegen den Realm authentifiziert bzw. für die sich Nutzer einloggen. Jede Anwendung (Frontend, Backend, Test-Tool) bekommt ihren eigenen Client.
Public Client Kann kein Geheimnis sicher speichern (Browser-SPA, Mobile-App, Skript) — hat kein Client-Secret. Sicherheit kommt stattdessen über PKCE (siehe unten).
Confidential Client Läuft auf einem vertrauenswürdigen Server und kann ein Client-Secret geheim halten.
Realm-Rolle Eine Rolle, die global im gesamten Realm gilt (z. B. ROLE_BUYER). PROCUREX nutzt ausschließlich Realm-Rollen, keine Client-Rollen — einfacher, weil dieselbe Rolle für Frontend und Backend gilt.
Access Token Kurzlebiges JWT (bei uns 300 s / 5 min gültig), das ein Client bei jedem API-Aufruf im Authorization: Bearer …-Header mitschickt. Enthält u. a. die Rollen im Claim realm_access.roles.
Refresh Token Längerlebiges Token, mit dem ein Client ein neues Access Token holen kann, ohne den Nutzer erneut einzuloggen.
ID Token Nur beim Browser-Login relevant: enthält Infos über den eingeloggten Nutzer (Name, E-Mail) für die Anzeige im Frontend.
JWT (JSON Web Token) Signiertes, Base64-kodiertes JSON. procurex-app validiert nur die Signatur gegen Keycloaks öffentlichen Schlüssel (JWK Set) — fragt bei jedem Request nicht aktiv bei Keycloak nach ("Resource Server"-Pattern, siehe unten).

Grant Types (Flows) — wie kommt eine Anwendung an ein Token?

Keycloak unterstützt mehrere Wege, ein Token zu bekommen. PROCUREX nutzt bewusst

unterschiedliche für unterschiedliche Zwecke:

Flow Ablauf Wo genutzt
Authorization Code + PKCE Der Nutzer wird zur Keycloak-Login-Seite umgeleitet und gibt sein Passwort dort ein (nie in der eigenen App). Nach Login leitet Keycloak mit einem einmaligen "Code" zurück, den die App serverseitig gegen ein Token eintauscht. PKCE (Proof Key for Code Exchange) verhindert, dass ein Angreifer einen abgefangenen Code selbst einlösen kann — wichtig, weil procurex-frontend ein public Client ohne Secret ist. procurex-frontend (echter Nutzer-Login im Browser)
Direct Access Grants (Resource Owner Password Credentials, ROPC) Die App schickt Benutzername und Passwort direkt an den Token-Endpoint, ganz ohne Redirect oder Keycloak-Login-Seite. Einfach, aber unsicher für echte Anwendungen — die App sieht das Klartext-Passwort. Deshalb bei procurex-frontend deaktiviert. Für automatisierte Skripte ohne Browser (z. B. Lasttests) ist das trotzdem die pragmatische Wahl. procurex-loadtest (k6-Skripte, PRX-36)
Client Credentials Der Client authentifiziert sich mit seinem eigenen Secret, es ist gar kein Nutzer beteiligt — für Service-zu-Service-Aufrufe. aktuell bei PROCUREX nicht genutzt

Wie PROCUREX Tokens validiert (Resource-Server-Pattern)

procurex-app prüft eingehende Access Tokens selbst, ohne bei jedem Request Keycloak

zu fragen: Spring Security lädt einmalig Keycloaks öffentliche Schlüssel (JWK Set, siehe

PROCUREX_OIDC_JWK_SET_URI) und prüft damit lokal die JWT-Signatur und Gültigkeit. Die

Rollen kommen aus dem Claim realm_access.roles (siehe SecurityConfig.java). Das ist

schnell (kein Netzwerk-Roundtrip pro Request) und funktioniert auch, wenn Keycloak kurz

nicht erreichbar ist, solange das Token noch gültig ist.

Clients

Client Typ Flow(s) Zweck
procurex-frontend public, kein Secret Authorization Code + PKCE Angular-SPA-Login für echte Nutzer
procurex-backend confidential, bearer-only nur zur Dokumentation — Spring validiert JWTs direkt gegen den Realm-Issuer, braucht keinen eigenen Client-Aufruf
procurex-loadtest public, kein Secret Direct Access Grants PRX-36: k6-Lasttests holen sich damit echte Tokens für buyer1/approver1/supplieradmin1, ohne Browser. Bewusst ein eigener Client statt Direct Access Grants auf procurex-frontend zu aktivieren — additiv, rührt die produktive Login-Konfiguration nicht an, kann jederzeit gefahrlos gelöscht werden.

Redirect-URIs von procurex-frontend: http://procurex.localhost/, http://localhost:4200/

(lokales ng serve). procurex-loadtest braucht keine Redirect-URIs (kein Browser-Flow).

procurex-loadtest manuell anlegen

Dieser Client ist nicht in procurex-realm-export.json enthalten (die Datei ist eine

einzeilige ~27000-Token-JSON, die sich mit den verfügbaren Editier-Werkzeugen in dieser

Umgebung nicht automatisiert anpassen ließ — und Keycloak-Admin-Änderungen sind für den

Assistenten ohnehin per Sicherheits-Classifier blockiert). Falls das Realm neu importiert

wird, muss dieser Client daher manuell nachgezogen werden:

  1. http://keycloak.localhost/admin → Login admin/admin → Realm-Dropdown oben links auf

procurex stellen.

  1. Clients → Create client.
  2. General settings: Client type OpenID Connect, Client ID procurex-loadtest, Name

PROCUREX Lasttest-Client (k6, PRX-36)Next.

  1. Capability config: Client authentication AUS lassen (public Client, kein Secret).

Bei "Authentication flow" nur Direct access grants aktivieren, alle anderen

Checkboxen (Standard flow, Implicit flow, Service accounts roles) deaktivieren → Next.

  1. Login settings: alles leer lassen (keine Redirect-URIs nötig) → Save.

Rollen

ROLE_BUYER, ROLE_APPROVER, ROLE_SUPPLIER_ADMIN, ROLE_RECEIVING, ROLE_FINANCE,

ROLE_ADMIN — entsprechen den in Confluence Seite 10 bestätigten Rollen und den Rollenansichten

im Frontend (core/models/role.model.ts).

Testnutzer (Lernprojekt — keine echten Zugangsdaten für Produktivsysteme)

Passwort für alle: procurex123

Username Rolle Kostenstelle (cost_center, siehe unten)
buyer1 ROLE_BUYER – (nur Genehmiger brauchen das Attribut)
approver1 ROLE_APPROVER CC-100
supplieradmin1 ROLE_SUPPLIER_ADMIN
receiving1 ROLE_RECEIVING
finance1 ROLE_FINANCE
admin1 ROLE_ADMIN – (ROLE_ADMIN ist immer ein globaler Override, siehe unten)

Organisationsgrenze über cost_center (FR-010, seit 2026-08-17)

ApprovalService prüft seit dem FR-010-Abgleich gegen PROCUREX-PLANUNGSENTSCHEIDUNGEN.md

zusätzlich zur Rolle, ob die Kostenstelle des Genehmigers zur Kostenstelle der Bestellung passt

(siehe procurex-approval/.../ApprovalService.java, requireCostCenterMatch). ROLE_ADMIN

entscheidet weiterhin kostenstellenübergreifend.

Bewusste Grenze: Die Prüfung ist nicht fail-closed, wenn das Token gar keinen

cost_center-Claim trägt — nur ein tatsächlich vorhandener, abweichender Claim führt zu HTTP 403.

Grund: dieses Fail-open-Verhalten verhindert, dass die Regel bestehende E2E-/k6-/

Genehmigungsläufe blockiert, solange nicht jeder Genehmiger ein cost_center-Attribut besitzt.

Rollout-Status (29.08.2026): Auf der lokalen enterprise-infrastructure-Instanz durchgeführt —

approver1 trägt das Attribut cost_center=CC-100 (per Keycloak-Admin-REST-API gesetzt,

inklusive Aktivierung von „Unmanaged attributes“ im User-Profil, ohne die war das Attribut sonst

beim Speichern still verworfen worden). Der Protocol-Mapper greift für Tokens des Clients

procurex-frontend; der procurex-loadtest-Client hat den Mapper bewusst nicht (siehe Zweck des

Clients oben), k6-Lasttests sehen den Claim daher nicht und bleiben unverändert im Fail-open-Pfad.

Rollout auf einer bereits laufenden Instanz nachziehen

procurex-realm-export.json enthält seit diesem Commit einen cost_center-Protocol-Mapper

(oidc-usermodel-attribute-mapper) auf dem Client procurex-frontend. Bei einem frischen

Realm-Import ist damit nichts weiter zu tun außer den Nutzern das Attribut zu setzen (siehe

unten). Bei einer bereits laufenden Instanz (Realm schon importiert, bevor dieser Mapper

hinzukam) muss nachgezogen werden:

  1. http://keycloak.localhost/admin → Login admin/admin → Realm procurex.
  2. **Clients → procurex-frontend → Client scopes → procurex-frontend-dedicated → Mappers →

Add mapper → By configuration → User Attribute**.

  1. Name/Token-Claim-Name cost_center, User Attribute cost_center, "Add to ID token" und

"Add to access token" beide ANSave.

  1. Falls Keycloak beim Setzen des Attributs in Schritt 5 einen Fehler wie "nicht erlaubtes

Attribut" zeigt: Realm settings → User profile → Unmanaged attributes auf Enabled

setzen (neuere Keycloak-Versionen lehnen per Default nicht deklarierte Attribute ab).

  1. Users → approver1 → Attributes → Add attribute: Key cost_center, Value CC-100

Save.

Ohne diese manuellen Schritte bleibt die Regel wie oben beschrieben inaktiv (kein Fehlverhalten,

nur keine Durchsetzung) — konsistent mit docs/quality-gates.md, das denselben Ehrlichkeitsgrundsatz

für andere noch nicht scharf geschaltete Prüfungen anwendet.

Backend-Anbindung

procurex-app validiert JWTs als OAuth2 Resource Server gegen

http://keycloak.localhost/realms/procurex (siehe procurex-app/application.yml,

PROCUREX_OIDC_ISSUER_URI). Rollen kommen als realm_access.roles-Claim im Access Token.

Frontend-Anbindung

procurex-frontend nutzt Authorization Code + PKCE gegen denselben Realm/Client (siehe

procurex-frontend/README.md, Abschnitt „Login“).

Lasttest-Anbindung (PRX-36)

procurex-loadtest nutzt Direct Access Grants gegen denselben Realm — siehe

loadtests/README.md für die k6-Skripte und Ergebnisse.

Neu anlegen (falls Volume zurückgesetzt wurde)

Es gibt kein --import-realm beim Start (siehe shared/enterprise-infrastructure/docker-compose.yml — jedes

angeschlossene Projekt legt seinen Realm selbst an). Am schnellsten über die Admin-Konsole

(http://keycloak.localhost, admin/admin) → Realm importierenprocurex-realm-export.json

(enthält Clients + Rollen, keine Nutzer/Passwörter und nicht den Client

procurex-loadtest — siehe Abschnitt oben zum manuellen Nachziehen). Alternativ per

Admin-REST-API, siehe Kommandos in der Commit-Historie dieses Ordners.

⌂ Cockpit