OBS/Kostenpflichtige Module/RESTServer/Beispiel1: Unterschied zwischen den Versionen
Keine Bearbeitungszusammenfassung |
|||
| Zeile 20: | Zeile 20: | ||
<syntaxhighlight lang="pascal" line> | <syntaxhighlight lang="pascal" line> | ||
procedure Authenticate(oReader: TxRestReader; oWriter: TxRestWriter); | |||
var | var cUser : string; | ||
cPass : string; | cPass : string; | ||
cSql : string; | cSql : string; | ||
| Zeile 29: | Zeile 27: | ||
lOk : Boolean; | lOk : Boolean; | ||
begin | begin | ||
cUser := ''; | |||
cPass := ''; | |||
oReader.Str('username', cUser); | |||
oReader.Str('password', cPass); | |||
cSql := 'SELECT u_nr, u_name FROM benutzer' + | |||
' WHERE u_login = ' + DB_SQLVal(cUser) + | |||
' AND u_passwort_hash = ' + DB_SQLVal(HashPasswort(cPass)) + | |||
' AND u_aktiv = ' + DB_SQLVal('1'); | |||
// DB_SOpen liefert true, wenn die ABFRAGE lief - nicht, wenn ein | |||
// Datensatz gefunden wurde. Ohne die EoF-Pruefung wuerde jede | |||
// Anmeldung gelingen, sobald das SQL fehlerfrei ist. | |||
lOk := false; | |||
if (DB_SOpen(oDB, cSql, qUser)) then begin | |||
if (not qUser.EoF) then begin | |||
lOk := true; | |||
oWriter.Int('status' , 1); | |||
oWriter.Str('_OBS_JWT_ID' , qUser.A2C('u_nr')); | |||
oWriter.Str('_OBS_JWT_SUBJECT' , qUser.A2C('u_name')); | |||
oWriter.Str('_OBS_JWT_AUDIENCE', 'dashboard'); | |||
end; | end; | ||
end; | |||
DB_Close(qUser); | |||
if (not lOk) then begin | |||
oWriter.Int('status', 9); | |||
oWriter.Str('error' , 'Benutzer oder Passwort ist falsch'); | |||
end; | end; | ||
end; | end; | ||
| Zeile 87: | Zeile 70: | ||
<syntaxhighlight lang="pascal" line> | <syntaxhighlight lang="pascal" line> | ||
procedure _AddVar( | //------------------------------------------------------------------------------ | ||
var | // Ein Wert als Listeneintrag. Der Helfer bekommt den WRITER und schreibt an | ||
// der Stelle hinein, an der er aufgerufen wird - er gibt kein Fragment zurueck. | |||
// Der leere Feldname ist der Eintrag einer Liste. | |||
//------------------------------------------------------------------------------ | |||
procedure _AddVar(oWriter: TxRestWriter; const cVar: string); | |||
var cTitel : string; | |||
cData : string; | cData : string; | ||
cEinh : string; | cEinh : string; | ||
| Zeile 96: | Zeile 84: | ||
begin | begin | ||
if (ST_Variable(oDB, cVar, cTitel, cData, cEinh, lString, lAlign)) then begin | if (ST_Variable(oDB, cVar, cTitel, cData, cEinh, lString, lAlign)) then begin | ||
oWriter.ObjBegin(''); | |||
oWriter.Str('variable', cVar); | |||
oWriter.Str('titel' , cTitel); | |||
oWriter.Str('wert' , cData); | |||
oWriter.Str('einheit' , cEinh); | |||
oWriter.ObjEnd; | |||
end; | end; | ||
end; | end; | ||
procedure Get(oReader: TxRestReader; oWriter: TxRestWriter); | |||
begin | begin | ||
// Audience-Pruefung: nur Dashboard-Tokens akzeptieren | // Audience-Pruefung: nur Dashboard-Tokens akzeptieren. Die Ablehnung ist | ||
if ( | // ein 403 und keine 200 mit Fehlertext - oWriter.Error baut dieselbe | ||
// Huelle, die auch die Server-eigenen Fehler tragen. | |||
if (oReader.Param('_OBS_JWT_AUDIENCE') <> 'dashboard') then begin | |||
oWriter.Error(403, 'FORBIDDEN_ROLE', 'Token nicht für diese Anwendung ausgestellt'); | |||
exit; | exit; | ||
end; | end; | ||
// Die Antwort ist eine nackte Liste, kein Objekt: RootArr sagt das, und | |||
// zwar VOR dem ersten Eintrag. Ein ArrEnd gibt es dazu nicht - die | |||
// Wurzelklammer setzt der Server. | |||
oWriter.RootArr(); | |||
_AddVar(oWriter, 'WERT_1'); | |||
_AddVar(oWriter, 'WERT_2'); | |||
_AddVar(oWriter, 'WERT_3'); | |||
_AddVar(oWriter, 'WERT_4'); | |||
end; | end; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Aktuelle Version vom 9. September 2026, 13:32 Uhr
- A Preise aktualisieren
- C Personen übertragen
- E Kategorien verwalten
- G Kataloge verwalten
- I Merkliste übertragen
- K Varianten übertragen
- L Artikelvarianten übertragen
- M Referenzarten übertragen
- N Lagerbestände verwalten
- U Bestellungen einlesen
- V leere Passworte füllen
- W Update-Informationen zurücksetzen
- X Konfiguration
- Z Protokoll
Beispiel 1: Daten-Abruf mit JWT-Authentifizierung
In diesem Beispiel werden Steuerungs-Variablen aus OBS abgerufen und auf einer Webseite ausgegeben. Der Zugang ist mit JWT-Pflicht eingerichtet, der Konsument muss sich daher zuerst anmelden und einen Token bezogen haben.
Einrichtung in OBS
- Server-Profil: Standard-TLS-Profil Public-API mit Bindung 0.0.0.0:443
- Zugang: Web-Dashboard
- API-Key: zufaellig generiert
- JWT aktiv, JWT-Endpunkt oauth, JWT-Key zufaellig, JWT-Exp 60 (Minuten)
- CORS-Origins: https://dashboard.kunde.de
- Endpunkt: steuerung/v1, dem Profil Public-API zugeordnet
- Berechtigung: Zugang Web-Dashboard fuer Endpunkt steuerung/v1 freigeschaltet
JWT-Authentifizierungs-Skript (Zugang)
Wird ueber die Zugaenge-Liste mit F7 geoeffnet. Pruefen, ob Benutzername/Passwort gegen die OBS-Benutzerverwaltung passen.
procedure Authenticate(oReader: TxRestReader; oWriter: TxRestWriter);
var cUser : string;
cPass : string;
cSql : string;
qUser : TxFQuery;
lOk : Boolean;
begin
cUser := '';
cPass := '';
oReader.Str('username', cUser);
oReader.Str('password', cPass);
cSql := 'SELECT u_nr, u_name FROM benutzer' +
' WHERE u_login = ' + DB_SQLVal(cUser) +
' AND u_passwort_hash = ' + DB_SQLVal(HashPasswort(cPass)) +
' AND u_aktiv = ' + DB_SQLVal('1');
// DB_SOpen liefert true, wenn die ABFRAGE lief - nicht, wenn ein
// Datensatz gefunden wurde. Ohne die EoF-Pruefung wuerde jede
// Anmeldung gelingen, sobald das SQL fehlerfrei ist.
lOk := false;
if (DB_SOpen(oDB, cSql, qUser)) then begin
if (not qUser.EoF) then begin
lOk := true;
oWriter.Int('status' , 1);
oWriter.Str('_OBS_JWT_ID' , qUser.A2C('u_nr'));
oWriter.Str('_OBS_JWT_SUBJECT' , qUser.A2C('u_name'));
oWriter.Str('_OBS_JWT_AUDIENCE', 'dashboard');
end;
end;
DB_Close(qUser);
if (not lOk) then begin
oWriter.Int('status', 9);
oWriter.Str('error' , 'Benutzer oder Passwort ist falsch');
end;
end;jeder Anmeldung desselben Benutzers. Das ist zulässig: der Server führt seine Sitzungen über eigene Kennungen (sid/rid), nicht über das jti des Skripts. Mehrfache und parallele Anmeldungen eines Benutzers sind damit
unproblematisch.Endpunkt-Skript steuerung/v1
Liefert eine Liste von Steuerungs-Werten zurueck. Die Auswertung der JWT-Claims sorgt dafuer, dass nur Audience dashboard Daten erhaelt.
//------------------------------------------------------------------------------
// Ein Wert als Listeneintrag. Der Helfer bekommt den WRITER und schreibt an
// der Stelle hinein, an der er aufgerufen wird - er gibt kein Fragment zurueck.
// Der leere Feldname ist der Eintrag einer Liste.
//------------------------------------------------------------------------------
procedure _AddVar(oWriter: TxRestWriter; const cVar: string);
var cTitel : string;
cData : string;
cEinh : string;
lString: Boolean;
lAlign : Boolean;
begin
if (ST_Variable(oDB, cVar, cTitel, cData, cEinh, lString, lAlign)) then begin
oWriter.ObjBegin('');
oWriter.Str('variable', cVar);
oWriter.Str('titel' , cTitel);
oWriter.Str('wert' , cData);
oWriter.Str('einheit' , cEinh);
oWriter.ObjEnd;
end;
end;
procedure Get(oReader: TxRestReader; oWriter: TxRestWriter);
begin
// Audience-Pruefung: nur Dashboard-Tokens akzeptieren. Die Ablehnung ist
// ein 403 und keine 200 mit Fehlertext - oWriter.Error baut dieselbe
// Huelle, die auch die Server-eigenen Fehler tragen.
if (oReader.Param('_OBS_JWT_AUDIENCE') <> 'dashboard') then begin
oWriter.Error(403, 'FORBIDDEN_ROLE', 'Token nicht für diese Anwendung ausgestellt');
exit;
end;
// Die Antwort ist eine nackte Liste, kein Objekt: RootArr sagt das, und
// zwar VOR dem ersten Eintrag. Ein ArrEnd gibt es dazu nicht - die
// Wurzelklammer setzt der Server.
oWriter.RootArr();
_AddVar(oWriter, 'WERT_1');
_AddVar(oWriter, 'WERT_2');
_AddVar(oWriter, 'WERT_3');
_AddVar(oWriter, 'WERT_4');
end;JavaScript-Client (Browser)
Im Browser werden zwei Aufrufe gemacht: erst Token holen, dann Daten lesen.
const API_BASE = 'https://api.meinserver.de';
const API_KEY = '[API-KEY]';
async function login(username, password) {
const res = await fetch(`${API_BASE}/oauth/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'apikey': API_KEY },
body: JSON.stringify({ username, password })
});
if (!res.ok) throw new Error('Login fehlgeschlagen');
const data = await res.json();
return data.token;
}
async function ladeSteuerung(token) {
const res = await fetch(`${API_BASE}/steuerung/v1`, {
method: 'GET',
headers: {
'apikey': API_KEY,
'Authorization': 'Bearer ' + token
}
});
if (!res.ok) throw new Error('Abruf fehlgeschlagen: ' + res.status);
return res.json();
}
async function zeigeTabelle() {
const token = await login('mitarbeiter', 'geheim');
const werte = await ladeSteuerung(token);
const tbl = document.getElementById('steuerung');
werte.forEach(w => {
const tr = document.createElement('tr');
tr.innerHTML = `<td>${w.titel}</td><td>${w.wert} ${w.einheit}</td>`;
tbl.appendChild(tr);
});
}
zeigeTabelle();Test mit curl
Token holen:
curl -X POST -H "apikey: [API-KEY]" -H "Content-Type: application/json" ^
-d "{\"username\":\"mitarbeiter\",\"password\":\"geheim\"}" ^
https://api.meinserver.de/oauth/
Antwort:
{"token": "eyJhbGciOi...",
"accessToken": "eyJhbGciOi...",
"expiresIn": 3600,
"serverTime": "2026-08-19T09:12:33+02:00"}
Da das Skript kein _OBS_JWT_REFRESH_ID liefert, enthält die Antwort kein refreshToken - der Client meldet sich nach Ablauf der 60 Minuten neu an.
Daten abrufen:
curl -H "apikey: [API-KEY]" -H "Authorization: Bearer eyJhbGciOi..." ^
https://api.meinserver.de/steuerung/v1
Was zeigt das Beispiel?
- Zweistufige Anmeldung: API-Key + JWT.
- Sichere Trennung von Konsumenten-Gruppen ueber die Audience-Claim.
- Eigenes Authentifizierungs-Skript pro Zugang.
- Browser-Zugriff ueber CORS-erlaubtes Origin.
- Anmeldung ohne Refresh-Token: einfachster Fall, der Token laeuft nach der Zeit aus JWT-Exp ab.