Zum Inhalt springen

OBS/Kostenpflichtige Module/RESTServer/Zugaenge: Unterschied zwischen den Versionen

Aus OBS Wiki
Rademacker (Diskussion | Beiträge)
Die Seite wurde neu angelegt: „{{Kostenpflichtige Module}} =Zugaenge= Ein '''Zugang''' (Account) ist der Kanal, ueber den ein externer Konsument den REST-Server anspricht. Er buendelt API-Key, Host-Beschraenkung, CORS-Konfiguration, optionale JWT-Authentifizierung und optionale mTLS-Subject-Pruefung. ==Aufruf== '''Stammdaten -> Z Weitere Stammdaten -> REST-Server -> Zugaenge''' ==Felder== {| class="wikitable" ! Feld !! Beschreibung |- | Name || Sprechender Name,…“
MINERVA-Rademacker (Diskussion | Beiträge)
Fehlercode AUTH_FAILED und Obergrenze der Refresh-Laufzeit ergaenzt [Volltext ersetzt]
 
(9 dazwischenliegende Versionen von einem anderen Benutzer werden nicht angezeigt)
Zeile 1: Zeile 1:
{{Kostenpflichtige Module}}
{{Kostenpflichtige Module}}


=Zugaenge=
=Zugänge=


Ein '''Zugang''' (Account) ist der Kanal, ueber den ein externer Konsument den REST-Server anspricht. Er buendelt API-Key, Host-Beschraenkung, CORS-Konfiguration, optionale JWT-Authentifizierung und optionale mTLS-Subject-Pruefung.
Ein '''Zugang''' (Account) ist der Kanal, über den ein externer Konsument den REST-Server anspricht. Er bündelt API-Key, Host-Beschränkung, CORS-Konfiguration, optionale JWT-Authentifizierung und optionale mTLS-Subject-Prüfung.


==Aufruf==
==Aufruf==


'''Stammdaten -> Z Weitere Stammdaten -> REST-Server -> Zugaenge'''
'''Stammdaten -> Z Weitere Stammdaten -> REST-Server -> Zugänge'''


==Felder==
==Felder==
Zeile 16: Zeile 16:
| Name          || Sprechender Name, erscheint im Protokoll und der Statistik
| Name          || Sprechender Name, erscheint im Protokoll und der Statistik
|-
|-
| Aktiv        || Inaktive Zugaenge werden bei der Authentifizierung mit 403 abgewiesen
| Aktiv        || Inaktive Zugänge werden bei der Authentifizierung mit 403 abgewiesen
|-
|-
| API-Key      || Eindeutiger Schluessel, den der Konsument im HTTP-Header ''apikey'' senden muss. Per Schluesselsymbol generierbar
| API-Key      || Eindeutiger Schlüssel, den der Konsument im HTTP-Header ''apikey'' senden muss. Per Schlüsselsymbol generierbar
|-
|-
| Host          || Optionaler Hostname oder IP-Adresse des Konsumenten. Stimmt die anfragende IP nicht ueberein, wird mit 403 abgelehnt
| Host          || Optionaler Hostname oder IP-Adresse des Konsumenten. Stimmt die anfragende IP nicht überein, wird mit 403 abgelehnt
|-
|-
| CORS-Origins  || Liste erlaubter Origins fuer Browser-Aufrufe, mehrere durch '';'' getrennt
| CORS-Origins  || Liste erlaubter Origins für Browser-Aufrufe, mehrere durch '';'' getrennt. '''Leer heisst nicht „keine Einschränkung", sondern „kein Browser-Zugriff"''' - siehe unten
|-
|-
| mTLS-Subject  || Erwarteter Substring im Client-Zertifikat-Subject (nur sinnvoll bei mTLS-Servern)
| mTLS-Subject  || Erwartete RDN-Komponente(n) im Client-Zertifikat-Subject, Komma-/Semikolon-getrennt (alle müssen vorkommen; nur sinnvoll bei mTLS-Servern)
|-
|-
| JWT          || Schalter, ob fuer diesen Zugang ein JWT-Token noetig ist
| JWT          || Schalter, ob für diesen Zugang ein JWT-Token nötig ist
|-
|-
| JWT-Endpunkt  || Adresse, ueber die der JWT-Token bezogen wird (z.B. ''oauth''), darf keinen ''/'' enthalten
| JWT-Endpunkt  || Pfad, über den '''alle Token-Vorgänge''' laufen: Anmelden, Erneuern und Abmelden (z.B. ''oauth'' oder ''meineapp/dev/auth''). Verglichen wird der '''vollständige Pfad''', mehrstufige Angaben mit ''/'' sind also erlaubt
|-
|-
| JWT-Key      || Geheimer Schluessel zur Signierung/Verifikation, per Schluesselsymbol generierbar
| JWT-Key      || Geheimer Schlüssel zur Signierung/Verifikation, per Schlüsselsymbol generierbar
|-
|-
| JWT-Exp      || Gueltigkeitsdauer eines ausgestellten Tokens in Minuten
| JWT-Exp      || Gültigkeitsdauer eines ausgestellten Access-Tokens in '''Minuten'''. Derselbe Wert wird dem Konsumenten in der Token-Antwort als ''expiresIn'' in '''Sekunden''' mitgeteilt und steht dem Endpunkt-Skript als ''_OBS_JWT_EXP_SEC'' zur Verfügung
|}
|}


==API-Key==
==API-Key==


Der API-Key ist die Basis-Authentifizierung. Der Client uebergibt ihn im HTTP-Header:
Der API-Key ist die Basis-Authentifizierung. Der Client übergibt ihn im HTTP-Header:


  apikey: abcd-efgh-ijkl-mnop-qrstuvwxyz12
  apikey: abcd-efgh-ijkl-mnop-qrstuvwxyz12


Wird ein Aufruf ohne ''apikey''-Header gestellt, sucht der Server intern nach einem Zugang mit API-Key '''*''' - damit lassen sich (mit Vorsicht!) auch oeffentliche Endpunkte ohne Authentifizierung realisieren.
Wird ein Aufruf ohne ''apikey''-Header gestellt, sucht der Server intern nach einem Zugang mit API-Key '''*''' - damit lassen sich (mit Vorsicht!) auch öffentliche Endpunkte ohne Authentifizierung realisieren.


{{Hinweis|Ein Zugang mit API-Key ''*'' sollte nur fuer bewusst freigegebene Endpunkte verwendet werden und immer in Kombination mit einer Host-Beschraenkung oder CORS-Beschraenkung betrieben werden.}}
{{Hinweis|Ein Zugang mit API-Key ''*'' sollte nur für bewusst freigegebene Endpunkte verwendet werden und immer in Kombination mit einer Host-Beschränkung oder CORS-Beschränkung betrieben werden.}}


==Host-Beschraenkung==
{{Achtung|'''Nur für wirklich öffentliche Dateien.''' Ohne API-Key kennt der
Endpunkt weder Mandant noch Rolle - es gibt nichts, woran er eine Berechtigung
festmachen könnte ausser dem, was in der URL steht. Ein Wildcard-Zugang eignet
sich deshalb für Artefakte, die für jeden bestimmt sind (Test-APK, Handbuch),
'''nicht''' für Kundenbelege. Siehe
[[OBS/Kostenpflichtige Module/RESTServer/Datei-Download|Datei-Download]].}}


Ist im Feld '''Host''' ein Wert hinterlegt, akzeptiert der Server Anfragen unter diesem Zugang nur dann, wenn die Remote-IP exakt diesem Host entspricht oder durch DNS-Aufloesung auf diese IP zeigt. DNS-Aufloesungen werden 5 Minuten gecached.
{{Hinweis|Bei einem Wildcard-Zugang sollte ''CORS-Origin'' '''leer''' bleiben.
Ein ''*'' dort wirkt auf die Preflight-Antwort '''aller''' Zugänge (siehe unten);
ein Download über die Adresszeile des Browsers sendet ohnehin kein ''Origin'',
CORS greift dort also gar nicht.}}
 
==Host-Beschränkung==
 
Ist im Feld '''Host''' ein Wert hinterlegt, akzeptiert der Server Anfragen unter diesem Zugang nur dann, wenn die Remote-IP exakt diesem Host entspricht oder durch DNS-Auflösung auf diese IP zeigt. DNS-Auflösungen werden 5 Minuten gecached.


==CORS==
==CORS==
Zeile 59: Zeile 71:
Ablauf:
Ablauf:


# Browser sendet einen Preflight-Request (''OPTIONS''). Der REST-Server prueft das Origin gegen die globale Origin-Liste aller Accounts (Cache, TTL 60s) und antwortet mit 204 + CORS-Header, falls erlaubt.
# Browser sendet einen Preflight-Request (''OPTIONS''). Der REST-Server prüft das Origin gegen die globale Origin-Liste '''aller''' Accounts (Cache, TTL 60s) und antwortet mit 204 + CORS-Header, falls erlaubt.
# Der eigentliche Request wird gegen die Origin-Liste des aktuell verwendeten Zugangs geprueft.
# Der eigentliche Request wird gegen die Origin-Liste '''des aktuell verwendeten Zugangs''' geprüft.
# Stimmt das Origin nicht, wird mit 403 ''Origin nicht erlaubt fuer Account ...'' abgelehnt.
# Stimmt das Origin nicht, wird mit 403 ''Origin nicht erlaubt für Account ...'' abgelehnt.


Erlaubte Methoden: ''GET, POST, PUT, DELETE, PATCH''. Erlaubte Header: ''Content-Type, apikey, Authorization, Accept''. Diese Werte sind systemseitig festgelegt und werden in der Preflight-Antwort zurueckgegeben.
Der Preflight kann nicht zugangsbezogen prüfen: der Browser schickt dabei keinen
''apikey'' mit, der Zugang ist zu diesem Zeitpunkt unbekannt. '''Die Durchsetzung
liegt deshalb auf Schritt 2''', nicht auf dem Preflight. Praktische Folge: trägt
'''ein''' Zugang auf dem Server ein ''*'' in seiner Liste, antwortet jeder
Preflight mit ''Access-Control-Allow-Origin: *'' - auch für Zugänge mit engerer
Liste. Deren eigentliche Anfrage wird trotzdem gegen die eigene Liste geprüft.
 
Erlaubte Methoden: ''GET, POST, PUT, DELETE, PATCH'' (systemseitig festgelegt).
 
Bei den '''Anfrage-Headern''' spiegelt der Server im Preflight zurück, was der
Browser in ''Access-Control-Request-Headers'' angefragt hat. Damit sind eigene
Header eines Projekts - ''Idempotency-Key'', ''If-Match'', später
''Upload-Offset''/''Upload-Length'' - ohne Änderung am Server nutzbar. Fragt der
Preflight keine Header an, antwortet der Server mit der Liste
''Content-Type, apikey, Authorization, Accept''.
 
{{Hinweis|Die Header-Liste ist '''keine Schutzgrenze''' - das ist das Origin. Sie
sagt dem Browser nur, welche Header der Server versteht. Dazu kommt, dass die
Zugangsdaten dieser Schnittstelle in Headern stehen (''apikey'',
''Authorization'') und nicht in Cookies: es gibt also keine automatisch
mitgeschickte Berechtigung, die eine fremde Seite ausnutzen könnte.}}
 
===Was „leer" bedeutet===
 
{{Achtung|Ein '''leeres''' Feld CORS-Origins heisst '''nicht''' „keine
Einschränkung". Es heisst: '''jeder Aufruf mit ''Origin''-Header wird mit 403
abgelehnt'''. Für Clients '''ohne''' ''Origin''-Header - mobile Apps, Server,
curl - ändert das Feld gar nichts, sie werden nie gegen CORS geprüft.}}
 
Daraus ergeben sich drei sinnvolle Einstellungen:
 
{| class="wikitable"
! Feld !! Wirkung
|-
| leer || Kein Browser-Zugriff. Richtig für Zugänge, die nur von Apps oder Servern benutzt werden
|-
| konkrete Origins || Browser-Zugriff nur von diesen Seiten. Der Regelfall für Web-Anwendungen
|-
| ''*'' || Browser-Zugriff von überall. Wirkt zusätzlich auf die Preflight-Antwort aller anderen Zugänge - bewusst und sparsam verwenden
|}
 
Für eine Browser-Anwendung ist dieses Feld besonders wichtig, weil ihr API-Key
zwangsläufig im JavaScript liegt und damit öffentlich ist. Die Origin-Liste
verhindert dann, dass jemand den Key nimmt und von seiner eigenen Seite benutzt.
Sie ist allerdings kein Ersatz für eine Zugriffskontrolle: wer den Key hat,
umgeht CORS mit einem einzigen Aufruf ausserhalb des Browsers.


==JWT-Authentifizierung==
==JWT-Authentifizierung==


Wird ein Zugang mit aktiver JWT-Pflicht aufgerufen, sind zwei Phasen zu durchlaufen:
Wird ein Zugang mit aktiver JWT-Pflicht aufgerufen, sind die folgenden Phasen zu durchlaufen (Phase 3 nur bei genutztem Refresh).
 
'''Anmelden, Erneuern und Abmelden laufen über denselben Endpunkt''' - den im
Feld JWT-Endpunkt hinterlegten Pfad. Ein eigener Endpunkt fürs Abmelden ist
nicht nötig und wäre auch umständlicher: der JWT-Endpunkt greift '''vor''' der
Adressauflösung und braucht deshalb weder eine Zeile in ''RESTSRV_ENDPOINTS''
noch eine Berechtigung in ''RESTSRV_ACCESS''.
 
Unterschieden wird nach Verb und Token-Art:
 
{| class="wikitable"
! Aufruf !! Ablauf
|-
| ''POST'' ohne ''Authorization'' || Anmelden (Skript-Methode ''Authenticate'')
|-
| ''POST'' mit Bearer, Refresh-Token || Erneuern (Skript-Methode ''Refresh'')
|-
| ''DELETE'' mit Bearer, Access-Token || Abmelden (kein Skript nötig)
|-
| ''POST'' mit Bearer, Access-Token || '''401''' - unverändert
|}
 
{{Hinweis|Dass das Abmelden am Verb hängt und nicht allein an der Token-Art, ist
Absicht. Würde ein vorgelegtes Access-Token beim ''POST'' als Abmeldung gedeutet,
würde ein Client, der seine beiden Token verwechselt, den Anwender still
abmelden - statt das klare 401 zu bekommen, an dem der Fehler auffällt.}}


===Phase 1: Token-Bezug===
===Phase 1: Token-Bezug===
Zeile 77: Zeile 159:
  Body:  {"username":"...", "password":"..."}
  Body:  {"username":"...", "password":"..."}


Der Server fuehrt das im Zugang hinterlegte '''Authentifizierungs-Skript''' aus (F7 in der Zugangs-Liste). Das Skript muss eine Funktion ''Authenticate'' bereitstellen, die ein JSON-Objekt mit:
Der Server führt das im Zugang hinterlegte '''Authentifizierungs-Skript''' aus (F7 in der Zugangs-Liste). Das Skript muss eine Prozedur ''Authenticate'' bereitstellen, die über den Writer schreibt:


* ''status'' = ''1'' bei Erfolg, ''9'' bei Misserfolg
* ''status'' = ''1'' bei Erfolg, ''9'' bei Misserfolg
* ''error'' = Fehlertext (bei status=9)
* ''error'' = Fehlertext (bei status=9)
* ''_OBS_JWT_ID'', ''_OBS_JWT_SUBJECT'', ''_OBS_JWT_AUDIENCE'' fuer die JWT-Claims (optional)
* ''_OBS_JWT_ID'', ''_OBS_JWT_SUBJECT'', ''_OBS_JWT_AUDIENCE'' für die JWT-Claims (optional)
* ''_OBS_JWT_CLAIM_<name>'' für beliebige Custom-Claims (z.B. Mandant ''tenant'', Rollen ''roles''); im Folge-Skript lesbar
* ''_OBS_JWT_REFRESH_ID'' (optional) löst die Ausstellung eines Refresh-Tokens aus; ''_OBS_JWT_REFRESH_EXP'' setzt dessen Lebensdauer in Minuten (Default 90 Tage)


zurueckliefert. Bei Erfolg signiert der Server einen Token (HS256, Issuer ''OBS REST-Server'', Lebenszeit gemaess JWT-Exp) und antwortet:
{{Achtung|Die Lebensdauer eines Refresh-Tokens ist nach oben auf '''372 Tage'''
begrenzt. Ein mit ''_OBS_JWT_REFRESH_EXP'' länger ausgestelltes Token wird bei der
Vorlage '''nicht mehr verifiziert''' und mit 401 abgewiesen - der Fehler fällt
also erst Monate später auf, beim ersten Erneuerungsversuch.}}


  {"token": "eyJhbGciOi..."}
Die Signatur lautet ''procedure Authenticate(oReader: TxRestReader; oWriter: TxRestWriter);'' - Details unter [[OBS/Kostenpflichtige Module/RESTServer/Scripting|Scripting]]. Bei Erfolg signiert der Server einen Token (HS256, Issuer ''OBS REST-Server'', Lebenszeit gemäß JWT-Exp) und antwortet:
 
  {"token":       "eyJhbGciOi...",
  "accessToken":  "eyJhbGciOi...",
  "expiresIn":    28800,
  "serverTime":  "2026-06-29T15:30:12+02:00"}
 
Zum Aufbau der Antwort:
 
* ''token'' und ''accessToken'' enthalten '''denselben''' Token. ''accessToken'' ist der Name, den die meisten Client-Bibliotheken erwarten; ''token'' bleibt für bestehende Konsumenten erhalten.
* ''expiresIn'' ist die Gültigkeit in '''Sekunden''' (JWT-Exp × 60).
* '''Alle weiteren Felder, die das Authenticate-Skript zurückgibt, werden mit ausgeliefert''' - mit Ausnahme von ''status'' und den ''_OBS_''-Steuerfeldern. So kann das Skript z.B. eine Benutzer-Id, den Mandanten oder Rollen direkt in die Anmelde-Antwort legen, ohne dass der Client dafür einen zweiten Aufruf braucht.
 
Beispiel mit Zusatzfeldern aus dem Skript:
 
{"benutzerId": "4711", "tenant": "nord", "roles": ["TECHNIKER"],
  "token": "eyJ...", "accessToken": "eyJ...", "refreshToken": "eyJ...",
  "expiresIn": 28800, "serverTime": "2026-06-29T15:30:12+02:00"}
 
Mit der Ausstellung legt der Server zu dem Token-Paar eine '''Sitzungszeile''' in
''RESTSRV_TOKEN'' an. Lässt sich diese Zeile nicht schreiben (Tabelle fehlt,
Datenbankproblem), wird '''kein Token ausgeliefert''': der Server antwortet mit
'''503''' und ''Retry-After''. Das ist bewusst von der abgelehnten Anmeldung
unterschieden - die Zugangsdaten waren in Ordnung, der Versuch darf wiederholt
werden.
 
Wird die Anmeldung abgelehnt (''status'' = 9), antwortet der Server mit
'''401 Unauthorized''', dem Code ''AUTH_FAILED'' und reicht den Text aus dem Feld
''error'' als ''error.message'' an den Client durch - der Konsument kann ihn also
direkt anzeigen. Die Formulierung liegt damit beim Skript.
 
{{Hinweis|''AUTH_FAILED'' unterscheidet die '''abgelehnte Anmeldung''' vom
''AUTH_EXPIRED'', das ein abgelaufenes oder gesperrtes Token liefert. Ein Client
sollte auf den Code reagieren und nicht auf den Text: Bei ''AUTH_FAILED'' gehört
die Anmeldemaske noch einmal vorgelegt, bei ''AUTH_EXPIRED'' erst ein Refresh
versucht.}}
 
{{Hinweis|Der durchgereichte Text ist für den Endanwender gedacht und sollte
keine internen Details verraten. „Benutzer oder Passwort ist falsch" ist eine gute
Meldung, „Kein Datensatz in BENUTZ mit b_name=meier" nicht.}}


===Phase 2: Token-Verwendung===
===Phase 2: Token-Verwendung===
Zeile 95: Zeile 221:
         Authorization: Bearer eyJhbGciOi...
         Authorization: Bearer eyJhbGciOi...


Der Server verifiziert Signatur, Issuer, Ausstellzeitpunkt und Ablauf (Toleranz: 20 Sekunden Clock-Skew). Im Skript stehen die Claims als Parameter ''_OBS_JWT_ID'', ''_OBS_JWT_SUBJECT'' und ''_OBS_JWT_AUDIENCE'' zur Verfuegung.
Der Server verifiziert Signatur, Issuer, Ausstellzeitpunkt und Ablauf (Toleranz: 20 Sekunden Clock-Skew) und prüft anschliessend die '''Sitzung''' in ''RESTSRV_TOKEN''. Im Skript stehen die Claims als Parameter ''_OBS_JWT_ID'', ''_OBS_JWT_SUBJECT'' und ''_OBS_JWT_AUDIENCE'' zur Verfügung.
 
Die Sitzungsprüfung weist ein Token ab, das zu keiner Zeile gehört oder dessen
Sitzung gesperrt wurde - beides mit '''401''' und dem Code ''AUTH_EXPIRED''. Der
Grund steht jeweils im Protokoll '''RESTSRV_PROTO''' unter ''TokenStore: ...''.
 
{{Hinweis|Ein Token, das '''vor''' der Einführung dieser Prüfung ausgestellt
wurde, gehört zu keiner Sitzung und wird abgewiesen. Nach dem Update müssen sich
angemeldete Konsumenten also einmalig neu anmelden.}}
 
===Phase 3: Token erneuern (Refresh)===
 
Soll die App lange ohne Neuanmeldung arbeiten, stellt das Authenticate-Skript zusätzlich ein Refresh-Token aus (Feld ''_OBS_JWT_REFRESH_ID''). Zum Erneuern ruft der Client '''denselben JWT-Endpunkt''' auf, aber mit dem Refresh-Token im ''Authorization''-Header:
 
POST https://api.meinserver.de/oauth/
Header: apikey: [API-KEY]
        Authorization: Bearer eyJhbGciOi... (Refresh-Token)
 
Der Server erkennt am Bearer-Token den Refresh-Fall, verifiziert das Token (Signatur, Ablauf, ''token_use=refresh''), '''entwertet es in der Sitzung''' und ruft erst dann im selben Skript die Methode '''Refresh''' auf. Das Skript liefert nur noch die aktuellen Claims zurück - Rotation, Einmalgebrauch und Sperrliste erledigt der Server. Die Antwort ist ein rotiertes Paar ''{"token","accessToken","refreshToken","expiresIn","serverTime"}'' in '''derselben Sitzung'''.
 
{{Achtung|Eine '''eigene Sperrtabelle im Skript ist nicht mehr nötig''' und sollte
entfernt werden. Wer sie weiterführt, rotiert zweimal: einmal im Skript, einmal im
Server. Der Server sieht die Skript-Tabelle nicht, und das Skript sieht die
Sitzung nicht.}}
 
Verhalten bei einem bereits benutzten Refresh-Token:
 
{| class="wikitable"
! Lage !! Antwort !! Wirkung auf die Sitzung
|-
| Erneute Vorlage '''innerhalb von 60 Sekunden''' || 401 ''AUTH_EXPIRED'' || Keine. Das ist der Normalfall einer App, deren parallele Anfragen gleichzeitig in den Refresh laufen - eine gewinnt, die anderen sollen die vom Gewinner gespeicherten Token benutzen
|-
| Erneute Vorlage '''später''' || 401 ''AUTH_EXPIRED'' || Die '''gesamte Sitzung wird gesperrt'''. Das Token wurde kopiert oder wiederholt, ohne dass ein Gewinner es noch brauchen könnte; der Vorfall wird mit IP protokolliert
|}
 
Details und ein vollständiges Beispiel siehe [[OBS/Kostenpflichtige Module/RESTServer/Scripting|Scripting]].
 
===Phase 4: Abmelden (Logout)===
 
Abgemeldet wird mit einem '''DELETE''' auf denselben JWT-Endpunkt, das
Access-Token im ''Authorization''-Header:
 
DELETE https://api.meinserver.de/oauth/
Header: apikey: [API-KEY]
        Authorization: Bearer eyJhbGciOi... (Access-Token)
 
HTTP/1.1 204 No Content
 
Der Server sperrt die '''ganze Sitzung''' - also auch die noch nicht
abgelaufenen Access-Token aller vorherigen Erneuerungen. '''Ein Skript ist dafür
nicht nötig''': es gibt keine Methode ''Logout'', der Vorgang läuft komplett in
der Engine. Damit funktioniert das Abmelden für jeden Zugang mit aktiver
JWT-Pflicht, ohne dass am Authentifizierungs-Skript etwas geändert wird.
 
* Ein wiederholter Aufruf ist unschädlich und bleibt '''204''' - auch wenn die Sitzung längst gesperrt ist.
* Ein abgelaufenes oder ungültiges Token ergibt '''401''' (es wird schon bei der Prüfung abgewiesen).
* Ein Refresh-Token statt des Access-Tokens ergibt '''401'''.
* Lässt sich die Sitzung nicht sperren, antwortet der Server '''503''' mit ''Retry-After'' - ein Client darf nicht glauben, er sei abgemeldet, während sein Token weiterläuft.
 
====Alle Geräte abmelden====
 
Der Fall „Gerät verloren" ist bewusst '''nicht''' am JWT-Endpunkt untergebracht:
er braucht eine fachliche Entscheidung darüber, wer das darf. Dafür gibt es das
''oWriter.JwtRevoke'', das jeder gewöhnliche Endpunkt aufrufen kann:
 
<syntaxhighlight lang="pascal" line>
procedure Post(oReader: TxRestReader; oWriter: TxRestWriter);
begin
    // 'session' = nur diese Sitzung, 'all' = alle Sitzungen des Subjects
    oWriter.JwtRevoke('all');
    oWriter.Status(204);
    oWriter.NoBody();
end;
</syntaxhighlight>
 
{{Hinweis|''all'' setzt voraus, dass das Authenticate-Skript ''_OBS_JWT_SUBJECT''
gesetzt hat. Ohne Subject lässt sich die Menge der Sitzungen nicht bestimmen, und
der Aufruf antwortet mit 503.}}
 
==Woher ein Anmelder seine Identität bekommt==
 
'''Diese Frage entscheidet das Skript des Zugangs, nicht der Server.''' Das
Authentifizierungs-Skript liegt je Zugang in <code>ra_jwt_script</code> - und
damit gilt für jede Anwendung ihre eigene Regel.
 
Das ist keine Kleinigkeit, sondern der Punkt, an dem sich zwei Anwendungen
unterscheiden. Ein Beispiel aus dem Betrieb: Ein Zugang für eine
Techniker-Anwendung lehnt jeden Benutzer ohne Mitarbeiterzuordnung
(<code>BENUTZ.b_mitarbeiter</code>) ab - '''bewusst''', denn ein Techniker ohne
Zuordnung bekäme eine leere Auftragsliste und würde den Fehler bei sich suchen.
Für einen Kundenzugang wäre genau dieselbe Regel falsch: sie träfe '''jeden'''
Anmelder.
 
Wer einen zweiten Zugang für eine andere Anwendung anlegt, prüft deshalb als
Erstes: Woraus besteht die Identität dieser Anmelder, und welche Ablehnung ist
für sie richtig? Ein kopiertes Skript bringt die Regel der anderen Anwendung
mit, und sie fällt erst auf, wenn sich der erste echte Anmelder nicht anmelden
kann.
 
==Sitzungen (RESTSRV_TOKEN)==
 
Je ausgestelltem Token-Paar entsteht eine Zeile. Sie enthält die Sitzung, die
Zeilenkennung, das Subject, die beiden Laufzeiten, IP und Trace-ID der Ausstellung
sowie die Kennzeichen „verbraucht" und „gesperrt".


Details und ein vollstaendiges Beispiel siehe [[OBS/Kostenpflichtige Module/RESTServer/Scripting|Scripting]].
* '''Rotation und Widerruf sind getrennt.''' Ein Refresh entwertet nur das Refresh-Token; der zugehörige Access-Token bleibt bis zu seinem Ablauf gültig. Sonst bekämen parallel laufende Anfragen ein 401, obwohl niemand abgemeldet ist.
* '''Die Sitzung überlebt die Rotation.''' Alle Paare einer Anmeldung tragen dieselbe Sitzungskennung - deshalb kann ein Logout sie gemeinsam sperren.
* '''Aufräumen läuft automatisch''' beim Serverstart und danach höchstens stündlich. Gelöscht wird nur, was nichts mehr entscheiden kann: Zeilen, deren Access- '''und''' Refresh-Laufzeit vorbei sind. Eine verbrauchte Zeile bleibt bis dahin stehen, weil sonst die Erkennung eines später vorgelegten gestohlenen Refresh-Tokens verloren ginge.
* '''Kein Eingriff nötig.''' Die Tabelle wird nicht gepflegt; sie ist Diagnose-Material. Alle Abweisungen stehen als ''TokenStore: ...'' in '''RESTSRV_PROTO'''.


==mTLS-Subject-Pruefung==
==mTLS-Subject-Prüfung==


Wird im Feld '''mTLS-Subject''' ein Wert hinterlegt, prueft der Server zusaetzlich zur Standard-mTLS-Validierung, ob der Subject-DN des Client-Zertifikats den hinterlegten Text enthaelt (case-insensitiv, Substring-Match).
Wird im Feld '''mTLS-Subject''' ein Wert hinterlegt, prüft der Server zusätzlich zur Standard-mTLS-Validierung, ob der Subject-DN des (CA-validierten) Client-Zertifikats die hinterlegten '''RDN-Komponenten''' enthält. Verglichen wird jede Komponente '''vollständig''' (kein Substring-Match) und case-insensitiv; mehrere Komponenten werden durch Komma oder Semikolon getrennt und müssen '''alle''' im Subject vorkommen.


Beispiele fuer Subject-Strings im Cert:
Beispiele für Subject-Strings im Cert (OneLine-DN):


  /C=DE/O=Kunde GmbH/CN=client-prod.kunde.de
  /C=DE/O=Kunde GmbH/CN=client-prod.kunde.de
  /C=DE/O=Kunde GmbH/CN=client-test.kunde.de
  /C=DE/O=Kunde GmbH/CN=client-test.kunde.de


Mit '''CN=client-prod''' im Feld wird genau das prod-Cert akzeptiert, das test-Cert wird mit 401 abgewiesen. Damit lassen sich verschiedene Konsumenten ueber dieselbe CA klar voneinander trennen.
Mit '''CN=client-prod.kunde.de''' im Feld wird genau das prod-Cert akzeptiert, das test-Cert mit 401 abgewiesen. Da jede RDN-Komponente vollständig verglichen wird, matcht z.B. '''CN=client1''' nicht fälschlich auch '''CN=client10'''. Mehrere Bedingungen lassen sich mit Komma/Semikolon kombinieren, z.B. ''O=Kunde GmbH;CN=client-prod.kunde.de''.


==Listen-Funktionen==
==Listen-Funktionen==


In der Zugaenge-Liste (allein):
In der Zugänge-Liste (allein):


* '''Einfg''' - neuer Zugang
* '''Einfg''' - neuer Zugang
Zeile 118: Zeile 350:
* '''F4''' - Sortierung
* '''F4''' - Sortierung
* '''F7''' - JWT-Authentifizierungs-Skript bearbeiten
* '''F7''' - JWT-Authentifizierungs-Skript bearbeiten
* '''F8''' - Statistik fuer den Zugang
* '''F8''' - Statistik für den Zugang


Aus der Endpunkt-Liste via F6 geoeffnet (Auswahl fuer Berechtigung):
Aus der Endpunkt-Liste via F6 geöffnet (Auswahl für Berechtigung):


* '''F2''' - markierte Zugaenge dem Endpunkt zuordnen
* '''F2''' - markierte Zugänge dem Endpunkt zuordnen
* '''F5''' - Zugang in der Liste markieren / Markierung aufheben
* '''F5''' - Zugang in der Liste markieren / Markierung aufheben

Aktuelle Version vom 25. September 2026, 10:28 Uhr

Zugänge

Ein Zugang (Account) ist der Kanal, über den ein externer Konsument den REST-Server anspricht. Er bündelt API-Key, Host-Beschränkung, CORS-Konfiguration, optionale JWT-Authentifizierung und optionale mTLS-Subject-Prüfung.

Aufruf

Stammdaten -> Z Weitere Stammdaten -> REST-Server -> Zugänge

Felder

Feld Beschreibung
Name Sprechender Name, erscheint im Protokoll und der Statistik
Aktiv Inaktive Zugänge werden bei der Authentifizierung mit 403 abgewiesen
API-Key Eindeutiger Schlüssel, den der Konsument im HTTP-Header apikey senden muss. Per Schlüsselsymbol generierbar
Host Optionaler Hostname oder IP-Adresse des Konsumenten. Stimmt die anfragende IP nicht überein, wird mit 403 abgelehnt
CORS-Origins Liste erlaubter Origins für Browser-Aufrufe, mehrere durch ; getrennt. Leer heisst nicht „keine Einschränkung", sondern „kein Browser-Zugriff" - siehe unten
mTLS-Subject Erwartete RDN-Komponente(n) im Client-Zertifikat-Subject, Komma-/Semikolon-getrennt (alle müssen vorkommen; nur sinnvoll bei mTLS-Servern)
JWT Schalter, ob für diesen Zugang ein JWT-Token nötig ist
JWT-Endpunkt Pfad, über den alle Token-Vorgänge laufen: Anmelden, Erneuern und Abmelden (z.B. oauth oder meineapp/dev/auth). Verglichen wird der vollständige Pfad, mehrstufige Angaben mit / sind also erlaubt
JWT-Key Geheimer Schlüssel zur Signierung/Verifikation, per Schlüsselsymbol generierbar
JWT-Exp Gültigkeitsdauer eines ausgestellten Access-Tokens in Minuten. Derselbe Wert wird dem Konsumenten in der Token-Antwort als expiresIn in Sekunden mitgeteilt und steht dem Endpunkt-Skript als _OBS_JWT_EXP_SEC zur Verfügung

API-Key

Der API-Key ist die Basis-Authentifizierung. Der Client übergibt ihn im HTTP-Header:

apikey: abcd-efgh-ijkl-mnop-qrstuvwxyz12

Wird ein Aufruf ohne apikey-Header gestellt, sucht der Server intern nach einem Zugang mit API-Key * - damit lassen sich (mit Vorsicht!) auch öffentliche Endpunkte ohne Authentifizierung realisieren.

HINWEIS: Ein Zugang mit API-Key * sollte nur für bewusst freigegebene Endpunkte verwendet werden und immer in Kombination mit einer Host-Beschränkung oder CORS-Beschränkung betrieben werden.
ACHTUNG: Nur für wirklich öffentliche Dateien. Ohne API-Key kennt der

Endpunkt weder Mandant noch Rolle - es gibt nichts, woran er eine Berechtigung festmachen könnte ausser dem, was in der URL steht. Ein Wildcard-Zugang eignet sich deshalb für Artefakte, die für jeden bestimmt sind (Test-APK, Handbuch), nicht für Kundenbelege. Siehe

Datei-Download.
HINWEIS: Bei einem Wildcard-Zugang sollte CORS-Origin leer bleiben.

Ein * dort wirkt auf die Preflight-Antwort aller Zugänge (siehe unten); ein Download über die Adresszeile des Browsers sendet ohnehin kein Origin,

CORS greift dort also gar nicht.

Host-Beschränkung

Ist im Feld Host ein Wert hinterlegt, akzeptiert der Server Anfragen unter diesem Zugang nur dann, wenn die Remote-IP exakt diesem Host entspricht oder durch DNS-Auflösung auf diese IP zeigt. DNS-Auflösungen werden 5 Minuten gecached.

CORS

Soll ein Endpunkt aus einer Browser-Anwendung (Single-Page-App, JavaScript) heraus aufgerufen werden, muss das Origin der Anwendung in der CORS-Liste des Zugangs eingetragen sein.

Format der Liste:

https://app.kunde.de;https://test.kunde.de;https://localhost:8080

Ablauf:

  1. Browser sendet einen Preflight-Request (OPTIONS). Der REST-Server prüft das Origin gegen die globale Origin-Liste aller Accounts (Cache, TTL 60s) und antwortet mit 204 + CORS-Header, falls erlaubt.
  2. Der eigentliche Request wird gegen die Origin-Liste des aktuell verwendeten Zugangs geprüft.
  3. Stimmt das Origin nicht, wird mit 403 Origin nicht erlaubt für Account ... abgelehnt.

Der Preflight kann nicht zugangsbezogen prüfen: der Browser schickt dabei keinen apikey mit, der Zugang ist zu diesem Zeitpunkt unbekannt. Die Durchsetzung liegt deshalb auf Schritt 2, nicht auf dem Preflight. Praktische Folge: trägt ein Zugang auf dem Server ein * in seiner Liste, antwortet jeder Preflight mit Access-Control-Allow-Origin: * - auch für Zugänge mit engerer Liste. Deren eigentliche Anfrage wird trotzdem gegen die eigene Liste geprüft.

Erlaubte Methoden: GET, POST, PUT, DELETE, PATCH (systemseitig festgelegt).

Bei den Anfrage-Headern spiegelt der Server im Preflight zurück, was der Browser in Access-Control-Request-Headers angefragt hat. Damit sind eigene Header eines Projekts - Idempotency-Key, If-Match, später Upload-Offset/Upload-Length - ohne Änderung am Server nutzbar. Fragt der Preflight keine Header an, antwortet der Server mit der Liste Content-Type, apikey, Authorization, Accept.

HINWEIS: Die Header-Liste ist keine Schutzgrenze - das ist das Origin. Sie

sagt dem Browser nur, welche Header der Server versteht. Dazu kommt, dass die Zugangsdaten dieser Schnittstelle in Headern stehen (apikey, Authorization) und nicht in Cookies: es gibt also keine automatisch

mitgeschickte Berechtigung, die eine fremde Seite ausnutzen könnte.

Was „leer" bedeutet

ACHTUNG: Ein leeres Feld CORS-Origins heisst nicht „keine

Einschränkung". Es heisst: jeder Aufruf mit Origin-Header wird mit 403 abgelehnt. Für Clients ohne Origin-Header - mobile Apps, Server,

curl - ändert das Feld gar nichts, sie werden nie gegen CORS geprüft.

Daraus ergeben sich drei sinnvolle Einstellungen:

Feld Wirkung
leer Kein Browser-Zugriff. Richtig für Zugänge, die nur von Apps oder Servern benutzt werden
konkrete Origins Browser-Zugriff nur von diesen Seiten. Der Regelfall für Web-Anwendungen
* Browser-Zugriff von überall. Wirkt zusätzlich auf die Preflight-Antwort aller anderen Zugänge - bewusst und sparsam verwenden

Für eine Browser-Anwendung ist dieses Feld besonders wichtig, weil ihr API-Key zwangsläufig im JavaScript liegt und damit öffentlich ist. Die Origin-Liste verhindert dann, dass jemand den Key nimmt und von seiner eigenen Seite benutzt. Sie ist allerdings kein Ersatz für eine Zugriffskontrolle: wer den Key hat, umgeht CORS mit einem einzigen Aufruf ausserhalb des Browsers.

JWT-Authentifizierung

Wird ein Zugang mit aktiver JWT-Pflicht aufgerufen, sind die folgenden Phasen zu durchlaufen (Phase 3 nur bei genutztem Refresh).

Anmelden, Erneuern und Abmelden laufen über denselben Endpunkt - den im Feld JWT-Endpunkt hinterlegten Pfad. Ein eigener Endpunkt fürs Abmelden ist nicht nötig und wäre auch umständlicher: der JWT-Endpunkt greift vor der Adressauflösung und braucht deshalb weder eine Zeile in RESTSRV_ENDPOINTS noch eine Berechtigung in RESTSRV_ACCESS.

Unterschieden wird nach Verb und Token-Art:

Aufruf Ablauf
POST ohne Authorization Anmelden (Skript-Methode Authenticate)
POST mit Bearer, Refresh-Token Erneuern (Skript-Methode Refresh)
DELETE mit Bearer, Access-Token Abmelden (kein Skript nötig)
POST mit Bearer, Access-Token 401 - unverändert
HINWEIS: Dass das Abmelden am Verb hängt und nicht allein an der Token-Art, ist

Absicht. Würde ein vorgelegtes Access-Token beim POST als Abmeldung gedeutet, würde ein Client, der seine beiden Token verwechselt, den Anwender still

abmelden - statt das klare 401 zu bekommen, an dem der Fehler auffällt.

Phase 1: Token-Bezug

Der Konsument ruft den JWT-Endpunkt des Zugangs auf, z.B.:

POST https://api.meinserver.de/oauth/
Header: apikey: [API-KEY]
Body:   {"username":"...", "password":"..."}

Der Server führt das im Zugang hinterlegte Authentifizierungs-Skript aus (F7 in der Zugangs-Liste). Das Skript muss eine Prozedur Authenticate bereitstellen, die über den Writer schreibt:

  • status = 1 bei Erfolg, 9 bei Misserfolg
  • error = Fehlertext (bei status=9)
  • _OBS_JWT_ID, _OBS_JWT_SUBJECT, _OBS_JWT_AUDIENCE für die JWT-Claims (optional)
  • _OBS_JWT_CLAIM_<name> für beliebige Custom-Claims (z.B. Mandant tenant, Rollen roles); im Folge-Skript lesbar
  • _OBS_JWT_REFRESH_ID (optional) löst die Ausstellung eines Refresh-Tokens aus; _OBS_JWT_REFRESH_EXP setzt dessen Lebensdauer in Minuten (Default 90 Tage)
ACHTUNG: Die Lebensdauer eines Refresh-Tokens ist nach oben auf 372 Tage

begrenzt. Ein mit _OBS_JWT_REFRESH_EXP länger ausgestelltes Token wird bei der Vorlage nicht mehr verifiziert und mit 401 abgewiesen - der Fehler fällt

also erst Monate später auf, beim ersten Erneuerungsversuch.

Die Signatur lautet procedure Authenticate(oReader: TxRestReader; oWriter: TxRestWriter); - Details unter Scripting. Bei Erfolg signiert der Server einen Token (HS256, Issuer OBS REST-Server, Lebenszeit gemäß JWT-Exp) und antwortet:

{"token":        "eyJhbGciOi...",
 "accessToken":  "eyJhbGciOi...",
 "expiresIn":    28800,
 "serverTime":   "2026-06-29T15:30:12+02:00"}

Zum Aufbau der Antwort:

  • token und accessToken enthalten denselben Token. accessToken ist der Name, den die meisten Client-Bibliotheken erwarten; token bleibt für bestehende Konsumenten erhalten.
  • expiresIn ist die Gültigkeit in Sekunden (JWT-Exp × 60).
  • Alle weiteren Felder, die das Authenticate-Skript zurückgibt, werden mit ausgeliefert - mit Ausnahme von status und den _OBS_-Steuerfeldern. So kann das Skript z.B. eine Benutzer-Id, den Mandanten oder Rollen direkt in die Anmelde-Antwort legen, ohne dass der Client dafür einen zweiten Aufruf braucht.

Beispiel mit Zusatzfeldern aus dem Skript:

{"benutzerId": "4711", "tenant": "nord", "roles": ["TECHNIKER"],
 "token": "eyJ...", "accessToken": "eyJ...", "refreshToken": "eyJ...",
 "expiresIn": 28800, "serverTime": "2026-06-29T15:30:12+02:00"}

Mit der Ausstellung legt der Server zu dem Token-Paar eine Sitzungszeile in RESTSRV_TOKEN an. Lässt sich diese Zeile nicht schreiben (Tabelle fehlt, Datenbankproblem), wird kein Token ausgeliefert: der Server antwortet mit 503 und Retry-After. Das ist bewusst von der abgelehnten Anmeldung unterschieden - die Zugangsdaten waren in Ordnung, der Versuch darf wiederholt werden.

Wird die Anmeldung abgelehnt (status = 9), antwortet der Server mit 401 Unauthorized, dem Code AUTH_FAILED und reicht den Text aus dem Feld error als error.message an den Client durch - der Konsument kann ihn also direkt anzeigen. Die Formulierung liegt damit beim Skript.

HINWEIS: AUTH_FAILED unterscheidet die abgelehnte Anmeldung vom

AUTH_EXPIRED, das ein abgelaufenes oder gesperrtes Token liefert. Ein Client sollte auf den Code reagieren und nicht auf den Text: Bei AUTH_FAILED gehört die Anmeldemaske noch einmal vorgelegt, bei AUTH_EXPIRED erst ein Refresh

versucht.
HINWEIS: {{{1}}}

Phase 2: Token-Verwendung

Bei allen folgenden Aufrufen sendet der Client den Token im Authorization-Header:

GET https://api.meinserver.de/kalender/v1
Header: apikey: [API-KEY]
        Authorization: Bearer eyJhbGciOi...

Der Server verifiziert Signatur, Issuer, Ausstellzeitpunkt und Ablauf (Toleranz: 20 Sekunden Clock-Skew) und prüft anschliessend die Sitzung in RESTSRV_TOKEN. Im Skript stehen die Claims als Parameter _OBS_JWT_ID, _OBS_JWT_SUBJECT und _OBS_JWT_AUDIENCE zur Verfügung.

Die Sitzungsprüfung weist ein Token ab, das zu keiner Zeile gehört oder dessen Sitzung gesperrt wurde - beides mit 401 und dem Code AUTH_EXPIRED. Der Grund steht jeweils im Protokoll RESTSRV_PROTO unter TokenStore: ....

HINWEIS: Ein Token, das vor der Einführung dieser Prüfung ausgestellt

wurde, gehört zu keiner Sitzung und wird abgewiesen. Nach dem Update müssen sich

angemeldete Konsumenten also einmalig neu anmelden.

Phase 3: Token erneuern (Refresh)

Soll die App lange ohne Neuanmeldung arbeiten, stellt das Authenticate-Skript zusätzlich ein Refresh-Token aus (Feld _OBS_JWT_REFRESH_ID). Zum Erneuern ruft der Client denselben JWT-Endpunkt auf, aber mit dem Refresh-Token im Authorization-Header:

POST https://api.meinserver.de/oauth/
Header: apikey: [API-KEY]
        Authorization: Bearer eyJhbGciOi... (Refresh-Token)

Der Server erkennt am Bearer-Token den Refresh-Fall, verifiziert das Token (Signatur, Ablauf, token_use=refresh), entwertet es in der Sitzung und ruft erst dann im selben Skript die Methode Refresh auf. Das Skript liefert nur noch die aktuellen Claims zurück - Rotation, Einmalgebrauch und Sperrliste erledigt der Server. Die Antwort ist ein rotiertes Paar {"token","accessToken","refreshToken","expiresIn","serverTime"} in derselben Sitzung.

ACHTUNG: Eine eigene Sperrtabelle im Skript ist nicht mehr nötig und sollte

entfernt werden. Wer sie weiterführt, rotiert zweimal: einmal im Skript, einmal im Server. Der Server sieht die Skript-Tabelle nicht, und das Skript sieht die

Sitzung nicht.

Verhalten bei einem bereits benutzten Refresh-Token:

Lage Antwort Wirkung auf die Sitzung
Erneute Vorlage innerhalb von 60 Sekunden 401 AUTH_EXPIRED Keine. Das ist der Normalfall einer App, deren parallele Anfragen gleichzeitig in den Refresh laufen - eine gewinnt, die anderen sollen die vom Gewinner gespeicherten Token benutzen
Erneute Vorlage später 401 AUTH_EXPIRED Die gesamte Sitzung wird gesperrt. Das Token wurde kopiert oder wiederholt, ohne dass ein Gewinner es noch brauchen könnte; der Vorfall wird mit IP protokolliert

Details und ein vollständiges Beispiel siehe Scripting.

Phase 4: Abmelden (Logout)

Abgemeldet wird mit einem DELETE auf denselben JWT-Endpunkt, das Access-Token im Authorization-Header:

DELETE https://api.meinserver.de/oauth/
Header: apikey: [API-KEY]
        Authorization: Bearer eyJhbGciOi... (Access-Token)
HTTP/1.1 204 No Content

Der Server sperrt die ganze Sitzung - also auch die noch nicht abgelaufenen Access-Token aller vorherigen Erneuerungen. Ein Skript ist dafür nicht nötig: es gibt keine Methode Logout, der Vorgang läuft komplett in der Engine. Damit funktioniert das Abmelden für jeden Zugang mit aktiver JWT-Pflicht, ohne dass am Authentifizierungs-Skript etwas geändert wird.

  • Ein wiederholter Aufruf ist unschädlich und bleibt 204 - auch wenn die Sitzung längst gesperrt ist.
  • Ein abgelaufenes oder ungültiges Token ergibt 401 (es wird schon bei der Prüfung abgewiesen).
  • Ein Refresh-Token statt des Access-Tokens ergibt 401.
  • Lässt sich die Sitzung nicht sperren, antwortet der Server 503 mit Retry-After - ein Client darf nicht glauben, er sei abgemeldet, während sein Token weiterläuft.

Alle Geräte abmelden

Der Fall „Gerät verloren" ist bewusst nicht am JWT-Endpunkt untergebracht: er braucht eine fachliche Entscheidung darüber, wer das darf. Dafür gibt es das oWriter.JwtRevoke, das jeder gewöhnliche Endpunkt aufrufen kann:

procedure Post(oReader: TxRestReader; oWriter: TxRestWriter);
begin
    // 'session' = nur diese Sitzung, 'all' = alle Sitzungen des Subjects
    oWriter.JwtRevoke('all');
    oWriter.Status(204);
    oWriter.NoBody();
end;
HINWEIS: all setzt voraus, dass das Authenticate-Skript _OBS_JWT_SUBJECT

gesetzt hat. Ohne Subject lässt sich die Menge der Sitzungen nicht bestimmen, und

der Aufruf antwortet mit 503.

Woher ein Anmelder seine Identität bekommt

Diese Frage entscheidet das Skript des Zugangs, nicht der Server. Das Authentifizierungs-Skript liegt je Zugang in ra_jwt_script - und damit gilt für jede Anwendung ihre eigene Regel.

Das ist keine Kleinigkeit, sondern der Punkt, an dem sich zwei Anwendungen unterscheiden. Ein Beispiel aus dem Betrieb: Ein Zugang für eine Techniker-Anwendung lehnt jeden Benutzer ohne Mitarbeiterzuordnung (BENUTZ.b_mitarbeiter) ab - bewusst, denn ein Techniker ohne Zuordnung bekäme eine leere Auftragsliste und würde den Fehler bei sich suchen. Für einen Kundenzugang wäre genau dieselbe Regel falsch: sie träfe jeden Anmelder.

Wer einen zweiten Zugang für eine andere Anwendung anlegt, prüft deshalb als Erstes: Woraus besteht die Identität dieser Anmelder, und welche Ablehnung ist für sie richtig? Ein kopiertes Skript bringt die Regel der anderen Anwendung mit, und sie fällt erst auf, wenn sich der erste echte Anmelder nicht anmelden kann.

Sitzungen (RESTSRV_TOKEN)

Je ausgestelltem Token-Paar entsteht eine Zeile. Sie enthält die Sitzung, die Zeilenkennung, das Subject, die beiden Laufzeiten, IP und Trace-ID der Ausstellung sowie die Kennzeichen „verbraucht" und „gesperrt".

  • Rotation und Widerruf sind getrennt. Ein Refresh entwertet nur das Refresh-Token; der zugehörige Access-Token bleibt bis zu seinem Ablauf gültig. Sonst bekämen parallel laufende Anfragen ein 401, obwohl niemand abgemeldet ist.
  • Die Sitzung überlebt die Rotation. Alle Paare einer Anmeldung tragen dieselbe Sitzungskennung - deshalb kann ein Logout sie gemeinsam sperren.
  • Aufräumen läuft automatisch beim Serverstart und danach höchstens stündlich. Gelöscht wird nur, was nichts mehr entscheiden kann: Zeilen, deren Access- und Refresh-Laufzeit vorbei sind. Eine verbrauchte Zeile bleibt bis dahin stehen, weil sonst die Erkennung eines später vorgelegten gestohlenen Refresh-Tokens verloren ginge.
  • Kein Eingriff nötig. Die Tabelle wird nicht gepflegt; sie ist Diagnose-Material. Alle Abweisungen stehen als TokenStore: ... in RESTSRV_PROTO.

mTLS-Subject-Prüfung

Wird im Feld mTLS-Subject ein Wert hinterlegt, prüft der Server zusätzlich zur Standard-mTLS-Validierung, ob der Subject-DN des (CA-validierten) Client-Zertifikats die hinterlegten RDN-Komponenten enthält. Verglichen wird jede Komponente vollständig (kein Substring-Match) und case-insensitiv; mehrere Komponenten werden durch Komma oder Semikolon getrennt und müssen alle im Subject vorkommen.

Beispiele für Subject-Strings im Cert (OneLine-DN):

/C=DE/O=Kunde GmbH/CN=client-prod.kunde.de
/C=DE/O=Kunde GmbH/CN=client-test.kunde.de

Mit CN=client-prod.kunde.de im Feld wird genau das prod-Cert akzeptiert, das test-Cert mit 401 abgewiesen. Da jede RDN-Komponente vollständig verglichen wird, matcht z.B. CN=client1 nicht fälschlich auch CN=client10. Mehrere Bedingungen lassen sich mit Komma/Semikolon kombinieren, z.B. O=Kunde GmbH;CN=client-prod.kunde.de.

Listen-Funktionen

In der Zugänge-Liste (allein):

  • Einfg - neuer Zugang
  • Return - Zugang bearbeiten
  • F4 - Sortierung
  • F7 - JWT-Authentifizierungs-Skript bearbeiten
  • F8 - Statistik für den Zugang

Aus der Endpunkt-Liste via F6 geöffnet (Auswahl für Berechtigung):

  • F2 - markierte Zugänge dem Endpunkt zuordnen
  • F5 - Zugang in der Liste markieren / Markierung aufheben