Es gibt viele Optionen zur Authentifizierung von API-Aufrufen, von X.509-Client-Zertifikaten bis zur HTTP-Basisauthentifizierung. In den letzten Jahren hat sich jedoch mit OAuth 2.0- Zugriffstoken ein De-facto-Standard herausgebildet. Dies sind Authentifizierungsdaten, die vom Client an den API-Server weitergegeben und normalerweise als HTTP-Header übermittelt werden.
OAuth 2.0 ist jedoch ein Labyrinth miteinander verbundener Standards. Die Prozesse zum Ausgeben, Präsentieren und Validieren eines OAuth 2.0-Authentifizierungsflusses basieren häufig auf mehreren verwandten Standards. Zum Zeitpunkt des Schreibens dieses Artikels gibt es acht OAuth 2.0-Standards . Zugriffstoken sind ein typisches Beispiel dafür, da in der OAuth 2.0-Kernspezifikation ( RFC 6749 ) kein Format für Zugriffstoken festgelegt ist. In der realen Welt sind zwei Formate gebräuchlich:
Nach der Authentifizierung legt ein Client bei jeder HTTP-Anfrage seinen Zugriffstoken vor, um Zugriff auf geschützte Ressourcen zu erhalten. Die Validierung des Zugriffstokens ist erforderlich, um sicherzustellen, dass es tatsächlich von einem vertrauenswürdigen Identitätsanbieter (IdP) ausgestellt wurde und nicht abgelaufen ist. Da IdPs die von ihnen ausgegebenen JWTs kryptografisch signieren, können JWTs „offline“ validiert werden, ohne dass eine Laufzeitabhängigkeit vom IdP besteht. Typischerweise enthält ein JWT auch ein Ablaufdatum, welches ebenfalls überprüft werden kann. Das NGINX Plus- Modul auth_jwt
führt eine Offline-JWT-Validierung durch.
Opaque Tokens hingegen müssen validiert werden, indem sie an den IdP zurückgesendet werden, der sie ausgestellt hat. Dies hat jedoch den Vorteil, dass solche Token vom IdP widerrufen werden können, beispielsweise im Rahmen eines globalen Abmeldevorgangs, ohne dass zuvor angemeldete Sitzungen weiterhin aktiv bleiben. Bei einer globalen Abmeldung ist möglicherweise auch eine Validierung der JWTs mit dem IdP erforderlich.
In diesem Blog beschreiben wir, wie NGINX und NGINX Plus als OAuth 2.0 Relying Party fungieren können, indem sie Zugriffstoken zur Validierung an den IdP senden und nur Anfragen weiterleiten, die den Validierungsprozess bestehen. Wir besprechen die verschiedenen Vorteile der Verwendung von NGINX und NGINX Plus für diese Aufgabe und wie das Benutzererlebnis durch das Zwischenspeichern von Validierungsantworten für kurze Zeit verbessert werden kann. Für NGINX Plus zeigen wir auch, wie der Cache auf einen Cluster von NGINX Plus-Instanzen verteilt werden kann, indem der Schlüssel-Wert-Speicher mit dem JavaScript-Modul aktualisiert wird, wie in NGINX Plus R18 eingeführt.
Sofern nicht anders angegeben, gelten die Informationen in diesem Blog sowohl für NGINX Open Source als auch für NGINX Plus. Verweise auf NGINX Plus gelten nur für dieses Produkt.
Die Standardmethode zum Validieren von Zugriffstoken bei einem IdP wird als Token-Introspektion bezeichnet. RFC 7662 (OAuth 2.0 Token Introspection ) ist mittlerweile ein weithin unterstützter Standard, der eine JSON/REST-Schnittstelle beschreibt, die eine vertrauende Partei verwendet, um dem IdP ein Token vorzulegen, und der die Struktur der Antwort beschreibt. Es wird von vielen führenden IdP-Anbietern und Cloud-Anbietern unterstützt.
Unabhängig davon, welches Token-Format verwendet wird, führt die Durchführung der Validierung bei jedem Back-End-Dienst oder jeder Back-End-Anwendung zu viel doppeltem Code und unnötiger Verarbeitung. Verschiedene Fehlerbedingungen und Randfälle müssen berücksichtigt werden. Dies in jedem Backend-Dienst zu tun, führt zu Inkonsistenzen bei der Implementierung und in der Folge zu einer unvorhersehbaren Benutzererfahrung. Überlegen Sie, wie jeder Back-End-Dienst mit den folgenden Fehlerbedingungen umgehen könnte:
auth_request-
Moduls zum Validieren von TokenUm Code-Duplikation und die daraus resultierenden Probleme zu vermeiden, können wir NGINX verwenden, um Zugriffstoken im Namen von Backend-Diensten zu validieren. Dies hat eine Reihe von Vorteilen:
Wenn NGINX als Reverse-Proxy für eine oder mehrere Anwendungen fungiert, können wir das Modul auth_request
verwenden, um einen API-Aufruf an einen IdP auszulösen, bevor eine Anforderung an das Backend weitergeleitet wird. Wie wir gleich sehen werden, weist die folgende Lösung einen grundlegenden Fehler auf, sie führt jedoch die grundlegende Funktionsweise des Moduls auth_request
ein, auf die wir in späteren Abschnitten näher eingehen werden.
Die auth_request-
Direktive (Zeile 5) gibt den Speicherort für die Verarbeitung von API-Aufrufen an. Die Weiterleitung zum Backend (Zeile 6) erfolgt nur, wenn die Auth_Request
-Antwort erfolgreich ist. Der Auth_Request-
Speicherort wird in Zeile 9 definiert. Es wird als intern
gekennzeichnet, um zu verhindern, dass externe Clients direkt darauf zugreifen.
Die Zeilen 11–14 definieren verschiedene Attribute der Anfrage, sodass sie dem Token-Introspection-Anfrageformat entspricht. Beachten Sie, dass das in der Introspektionsanforderung gesendete Zugriffstoken eine Komponente des in Zeile 14 definierten Textkörpers ist. Hier gibt token=$http_apikey
an, dass der Client das Zugriffstoken im APIKEY-
Anforderungsheader bereitstellen muss. Natürlich kann das Zugriffstoken in jedem Attribut der Anforderung bereitgestellt werden. In diesem Fall verwenden wir eine andere NGINX-Variable.
auth_request
mit dem NGINX JavaScript-ModulWie bereits erwähnt, ist die Verwendung des Auth_Request-
Moduls auf diese Weise keine vollständige Lösung. Das Modul auth_request
verwendet HTTP-Statuscodes, um den Erfolg zu bestimmen ( 2xx
= gut, 4xx
= schlecht). Allerdings kodieren OAuth 2.0-Token-Introspektionsantworten Erfolg oder Misserfolg in einem JSON-Objekt und geben den HTTP-Statuscode zurück 200
(OK)
in beiden Fällen.
Was wir brauchen, ist ein JSON-Parser, der die Introspektionsantwort des IdP in den entsprechenden HTTP-Statuscode umwandelt, damit das Auth_Request-
Modul diese Antwort richtig interpretieren kann.
Glücklicherweise ist die JSON-Analyse für das NGINX JavaScript-Modul (njs) eine triviale Aufgabe. Anstatt also einen Standortblock
zum Ausführen der Token-Introspektionsanforderung zu definieren, weisen wir das Auth_Request
-Modul an, eine JavaScript-Funktion aufzurufen.
[ Herausgeber – Dieser Beitrag ist einer von mehreren, die Anwendungsfälle für das NGINX-JavaScript-Modul untersuchen. Eine vollständige Liste finden Sie unter Anwendungsfälle für das NGINX-JavaScript-Modul .
Der Code in diesem Abschnitt wird aktualisiert, um die Direktive js_import
zu verwenden, die die Direktive js_include
in NGINX Plus R23 und höher ersetzt. Weitere Informationen finden Sie in der Referenzdokumentation für das NGINX-JavaScript-Modul – der Abschnitt „Beispielkonfiguration“ zeigt die richtige Syntax für die NGINX-Konfiguration und JavaScript-Dateien. ]
Notiz: Diese Lösung erfordert, dass das JavaScript-Modul als dynamisches Modul mit der Direktive load_module
in nginx.conf geladen wird. Anweisungen finden Sie im NGINX Plus-Administratorhandbuch .
Die js_content-
Direktive in Zeile 13 gibt eine JavaScript-Funktion, introspectAccessToken
, als auth_request-
Handler an. Die Handler-Funktion ist in oauth2.js definiert:
Beachten Sie, dass die Funktion introspectAccessToken
eine HTTP-Unteranforderung (Zeile 2) an einen anderen Speicherort ( /oauth2_send_request ) sendet, der im folgenden Konfigurationsausschnitt definiert ist. Der JavaScript-Code analysiert dann die Antwort (Zeile 5) und sendet basierend auf dem Wert des aktiven
Felds den entsprechenden Statuscode zurück an das Auth_Request-
Modul. Gültige (aktive) Tokens kehren zurück HTTP 204
(NEIN
Inhalt)
(aber erfolgreich) und ungültige Token werden zurückgegeben HTTP 403
(Verboten)
. Fehlerbedingungen werden zurückgegeben HTTP 401
(Nicht autorisiert)
damit Fehler von ungültigen Token unterschieden werden können.
Notiz: Dieser Code dient nur als Proof of Concept und hat keine Produktionsqualität. Eine Komplettlösung mit umfassender Fehlerbehandlung und Protokollierung finden Sie weiter unten .
Der in Zeile 2 definierte Zielort der Unteranfrage ähnelt sehr stark unserer ursprünglichen Auth_Request-
Konfiguration.
Die gesamte Konfiguration zum Erstellen der Token-Introspektionsanforderung ist am Speicherort /_oauth2_send_request enthalten. Die Authentifizierung (Zeile 19), das Zugriffstoken selbst (Zeile 21) und die URL für den Token-Introspection-Endpunkt (Zeile 22) sind normalerweise die einzigen erforderlichen Konfigurationselemente. Damit der IdP Token-Introspection-Anfragen von dieser NGINX-Instanz akzeptieren kann, ist eine Authentifizierung erforderlich. Die OAuth 2.0 Token Introspection- Spezifikation verlangt eine Authentifizierung, gibt aber nicht die Methode an. In diesem Beispiel verwenden wir ein Trägertoken im Autorisierungsheader
.
Wenn diese Konfiguration vorhanden ist und NGINX eine Anfrage empfängt, leitet es diese an das JavaScript-Modul weiter, das eine Token-Introspektionsanfrage an den IdP stellt. Die Antwort vom IdP wird überprüft und die Authentifizierung gilt als erfolgreich, wenn das aktive
Feld true
ist. Diese Lösung ist eine kompakte und effiziente Möglichkeit, eine OAuth 2.0-Token-Introspektion mit NGINX durchzuführen und kann problemlos für andere Authentifizierungs-APIs angepasst werden.
Aber wir sind noch nicht ganz fertig. Die größte Herausforderung bei der Token-Introspektion besteht im Allgemeinen darin, dass sie zu jeder einzelnen HTTP-Anfrage eine Latenzzeit hinzufügt. Dies kann zu einem erheblichen Problem werden, wenn es sich bei dem betreffenden IdP um eine gehostete Lösung oder einen Cloud-Anbieter handelt. NGINX und NGINX Plus können diesen Nachteil durch Zwischenspeichern der Introspektionsantworten optimieren.
Die OAuth 2.0-Token-Introspektion wird vom IdP an einem JSON/REST-Endpunkt bereitgestellt, sodass die Standardantwort ein JSON-Text mit HTTP-Status ist200
. Wenn diese Antwort mit dem Zugriffstoken abgeglichen wird, ist sie äußerst gut zwischenspeicherbar.
NGINX kann so konfiguriert werden, dass für jedes Zugriffstoken eine Kopie der Introspektionsantwort zwischengespeichert wird, sodass NGINX beim nächsten Vorlegen desselben Zugriffstokens die zwischengespeicherte Introspektionsantwort bereitstellt, anstatt einen API-Aufruf an den IdP zu tätigen. Dadurch wird die Gesamtlatenz für nachfolgende Anfragen erheblich verbessert. Wir können steuern, wie lange zwischengespeicherte Antworten verwendet werden, um das Risiko zu verringern, dass ein abgelaufener oder kürzlich widerrufener Zugriffstoken akzeptiert wird. Wenn ein API-Client beispielsweise typischerweise mehrere API-Aufrufe in einem kurzen Zeitraum durchführt, kann eine Cache-Gültigkeit von 10 Sekunden ausreichen, um das Nutzererlebnis messbar zu verbessern.
Die Zwischenspeicherung wird durch die Angabe des Speicherorts aktiviert: ein Verzeichnis auf der Festplatte für den Cache (Introspektionsantworten) und eine gemeinsam genutzte Speicherzone für die Schlüssel (Zugriffstoken).
Die Direktive proxy_cache_path
weist den erforderlichen Speicher zu: /var/cache/nginx/oauth für die Introspektionsantworten und eine Speicherzone namens token_responses für die Schlüssel. Es wird im HTTP-
Kontext konfiguriert und erscheint daher außerhalb der Server-
und Standortblöcke
. Das Caching selbst wird dann innerhalb des Standortblocks
aktiviert, in dem die Antworten zur Token-Introspektion verarbeitet werden:
Das Caching wird für diesen Speicherort mit der Direktive „proxy_cache“
(Zeile 26) aktiviert. Standardmäßig speichert NGINX die Caches basierend auf der URI, in unserem Fall möchten wir die Antwort jedoch basierend auf dem Zugriffstoken im APIKEY-
Anforderungsheader (Zeile 27) zwischenspeichern.
In Zeile 28 verwenden wir die Direktive proxy_cache_lock
, um NGINX mitzuteilen, dass es bei gleichzeitig eingehenden Anfragen mit demselben Cache-Schlüssel warten muss, bis die erste Anfrage den Cache gefüllt hat, bevor es auf die anderen antwortet. Die Direktive proxy_cache_valid
(Zeile 29) teilt NGINX mit, wie lange die Introspektionsantwort zwischengespeichert werden soll. Ohne diese Anweisung ermittelt NGINX die Caching-Zeit anhand der vom IdP gesendeten Cache-Control-Header. Diese sind jedoch nicht immer zuverlässig. Deshalb weisen wir NGINX auch an, Header zu ignorieren , die sonst die Art und Weise beeinflussen würden, wie wir Antworten zwischenspeichern (Zeile 30).
Wenn das Caching jetzt aktiviert ist, entstehen einem Client, der ein Zugriffstoken vorlegt, nur die Latenzkosten, die entstehen, wenn alle 10 Sekunden eine Token-Introspektionsanforderung gestellt wird.
Durch die Kombination von Inhalts-Caching und Token-Introspektion lässt sich die Gesamtleistung einer Anwendung äußerst effektiv verbessern, ohne dass die Auswirkungen auf die Sicherheit nennenswert wären. Wenn NGINX jedoch verteilt bereitgestellt wird, beispielsweise über mehrere Rechenzentren, Cloud-Plattformen oder einen Active‑Active-Cluster hinweg, stehen zwischengespeicherte Token-Introspektionsantworten nur der NGINX-Instanz zur Verfügung, die die Introspektionsanforderung ausgeführt hat.
Mit NGINX Plus können wir das Keyval-
Modul – einen In-Memory-Schlüssel-Wert-Speicher – verwenden, um Token-Introspektionsantworten zwischenzuspeichern. Darüber hinaus können wir diese Antworten mithilfe des Moduls zone_sync
auch über einen Cluster von NGINX Plus-Instanzen hinweg synchronisieren. Dies bedeutet, dass die Antwort bei allen NGINX Plus-Instanzen im Cluster verfügbar ist, unabhängig davon, welche NGINX Plus-Instanz die Token-Introspektionsanforderung ausgeführt hat.
Notiz: Die Konfiguration des Zone_Sync-
Moduls für die gemeinsame Nutzung des Laufzeitstatus liegt außerhalb des Rahmens dieses Blogs. Weitere Informationen zum Teilen des Status in einem NGINX Plus-Cluster finden Sie im NGINX Plus-Administratorhandbuch .
In NGINX Plus R18 und höher kann der Schlüssel-Wert-Speicher durch Ändern der Variable aktualisiert werden, die in der Direktive „keyval“
deklariert ist. Da das JavaScript-Modul Zugriff auf alle NGINX-Variablen hat, können während der Verarbeitung der Antwort Introspektionsantworten in den Schlüssel-Wert-Speicher eingefügt werden.
Wie der NGINX-Dateisystem-Cache wird der Schlüssel-Wert-Speicher durch Angabe seines Speichers aktiviert, in diesem Fall eine Speicherzone, die den Schlüssel (Zugriffstoken) und den Wert (Introspektionsantwort) speichert.
Beachten Sie, dass wir mit dem Timeout-
Parameter der Direktive keyval_zone
dieselbe Gültigkeitsdauer von 10 Sekunden für zwischengespeicherte Antworten angeben wie in Zeile 29 von auth_request_cache.conf , sodass jedes Mitglied des NGINX Plus-Clusters die Antwort unabhängig entfernt, wenn sie abläuft. Zeile 2 gibt das Schlüssel-Wert-Paar für jeden Eintrag an: Der Schlüssel ist das Zugriffstoken, das im APIKEY-
Anforderungsheader bereitgestellt wird, und der Wert ist die Introspektionsantwort, die von der Variable „$token_data“
ausgewertet wird.
Jetzt wird für jede Anfrage, die einen APIKEY-
Anforderungsheader enthält, die Variable $token_data
mit der vorherigen Token-Introspektionsantwort (sofern vorhanden) aufgefüllt. Daher aktualisieren wir den JavaScript-Code, um zu prüfen, ob wir bereits eine Token-Introspektionsantwort haben.
Zeile 2 prüft, ob für diesen Zugriffstoken bereits ein Key‑Value‑Store‑Eintrag vorhanden ist. Da es zwei Wege gibt, über die eine Introspektionsantwort abgerufen werden kann (aus dem Schlüssel-Wert-Speicher oder aus einer Introspektionsantwort), verschieben wir die Validierungslogik in die folgende separate Funktion, tokenResult
:
Jetzt wird jede Token-Introspektionsantwort im Schlüssel-Wert-Speicher gespeichert und mit allen anderen Mitgliedern des NGINX Plus-Clusters synchronisiert. Das folgende Beispiel zeigt eine einfache HTTP-Anfrage mit einem gültigen Zugriffstoken, gefolgt von einer Abfrage an die NGINX Plus-API, um den Inhalt des Schlüssel-Wert-Speichers anzuzeigen.
$ curl -IH "apikey: tQ7AfuEFvI1yI-XNPNhjT38vg_reGkpDFA" http://localhost/ HTTP/1.1 200 OK Datum: Mittwoch, 24. April 2019, 17:41:34 GMT Inhaltstyp: application/json Inhaltslänge: 612 $ curl http://localhost/api/4/http/keyvals/access_tokens {"tQ7AfuEFvI1yI-XNPNhjT38vg_reGkpDFA":"{\"active\":true}"}
Beachten Sie, dass der Schlüssel-Wert-Speicher selbst das JSON-Format verwendet, sodass bei der Antwort zur Token-Introspektion automatisch Escape-Zeichen auf die Anführungszeichen angewendet werden.
Eine nützliche Funktion der OAuth 2.0-Token-Introspektion besteht darin, dass die Antwort neben dem aktiven Status des Tokens auch Informationen über diesen enthalten kann. Zu diesen Informationen gehören das Ablaufdatum des Tokens und die Attribute des zugehörigen Benutzers: Benutzername, E-Mail-Adresse usw.
Diese zusätzlichen Informationen können sehr nützlich sein. Sie können protokolliert, zur Implementierung feinkörniger Zugriffskontrollrichtlinien verwendet oder Backend-Anwendungen bereitgestellt werden. Wir können jedes dieser Attribute in das auth_request
-Modul exportieren, indem wir sie als zusätzliche Antwortheader mit einer erfolgreichen (HTTP204
) Antwort.
Wir iterieren über jedes Attribut der Introspektionsantwort (Zeile 23) und senden es als Antwortheader an das Auth_Request-
Modul zurück. Jeder Header-Name ist mit dem Präfix Token-
versehen, um Konflikte mit Standard-Antwort-Headern zu vermeiden (Zeile 26). Diese Antwort-Header können jetzt in NGINX-Variablen konvertiert und als Teil der regulären Konfiguration verwendet werden.
In diesem Beispiel konvertieren wir das Benutzernamenattribut
in eine neue Variable, $username
(Zeile 11). Mit der Direktive „auth_request_set“
können wir den Kontext der Token-Introspektionsantwort in den Kontext der aktuellen Anfrage exportieren. Der Antwortheader für jedes Attribut (hinzugefügt durch den JavaScript-Code) ist als $sent_http_token_- Attribut
verfügbar. Zeile 12 enthält dann den Wert für $username
als Anforderungsheader, der an das Backend weitergeleitet wird. Wir können diese Konfiguration für alle Attribute wiederholen, die in der Token-Introspektionsantwort zurückgegeben werden.
Der obige Code und die Konfigurationsbeispiele sind funktionsfähig und für Proof-of-Concept- Tests oder die Anpassung an einen bestimmten Anwendungsfall geeignet. Für den Produktionseinsatz empfehlen wir dringend zusätzliche Fehlerbehandlung, Protokollierung und flexible Konfiguration. Eine robustere und ausführlichere Implementierung für NGINX und NGINX Plus finden Sie in unserem GitHub-Repo:
In diesem Blog haben wir gezeigt, wie das NGINX auth_request
-Modul in Verbindung mit dem JavaScript-Modul verwendet wird, um eine OAuth 2.0-Token-Introspektion bei Clientanforderungen durchzuführen. Darüber hinaus haben wir diese Lösung um Caching erweitert und Attribute aus der Introspektionsantwort zur Verwendung in der NGINX-Konfiguration extrahiert.
Wir haben außerdem beschrieben, wie der Schlüssel-Wert-Speicher von NGINX Plus als verteilter Cache für Introspektionsantworten verwendet werden kann, der für Produktionsbereitstellungen in einem Cluster von NGINX Plus-Instanzen geeignet ist.
Probieren Sie die OAuth 2.0-Token-Introspektion mit NGINX Plus selbst aus – starten Sie noch heute Ihre kostenlose 30-Tage-Testversion oder kontaktieren Sie uns, um Ihre Anwendungsfälle zu besprechen .
„Dieser Blogbeitrag kann auf Produkte verweisen, die nicht mehr verfügbar und/oder nicht mehr unterstützt werden. Die aktuellsten Informationen zu verfügbaren F5 NGINX-Produkten und -Lösungen finden Sie in unserer NGINX-Produktfamilie . NGINX ist jetzt Teil von F5. Alle vorherigen NGINX.com-Links werden auf ähnliche NGINX-Inhalte auf F5.com umgeleitet."