Ich habe versucht, ein ähnliches Problem anzugehen. Meine Benutzer müssen für jede Anfrage authentifiziert werden. Ich habe mich darauf konzentriert, die Benutzer mindestens einmal von der Backend-App authentifizieren zu lassen (Validierung des JWT-Tokens), aber danach habe ich beschlossen, das Backend nicht mehr zu benötigen.
Ich habe mich dafür entschieden, kein Nginx-Plugin zu benötigen, das standardmäßig nicht enthalten ist. Andernfalls können Sie Nginx-JWT- oder Lua-Skripte überprüfen, und dies wären wahrscheinlich großartige Lösungen.
Adressierungsauthentifizierung
Bisher habe ich Folgendes getan:
Delegierte die Authentifizierung mit an Nginx auth_request
. Dadurch wird ein internal
Speicherort aufgerufen, der die Anforderung an meinen Backend-Token-Validierungsendpunkt weiterleitet. Dies allein spricht das Problem der Behandlung einer hohen Anzahl von Validierungen überhaupt nicht an.
Das Ergebnis der Token-Validierung wird mithilfe einer proxy_cache_key "$cookie_token";
Direktive zwischengespeichert. Nach erfolgreicher Token-Validierung fügt das Backend eine Cache-Control
Anweisung hinzu, die Nginx anweist, das Token nur bis zu 5 Minuten zwischenzuspeichern. Zu diesem Zeitpunkt befindet sich jedes einmal validierte Authentifizierungstoken im Cache. Nachfolgende Anforderungen desselben Benutzers / Tokens berühren das Authentifizierungs-Backend nicht mehr!
Um meine Backend-App vor einer möglichen Überflutung durch ungültige Token zu schützen, habe ich auch abgelehnte Validierungen zwischengespeichert, wenn mein Backend-Endpunkt 401 zurückgibt. Diese werden nur für kurze Zeit zwischengespeichert, um zu vermeiden, dass der Nginx-Cache möglicherweise mit solchen Anforderungen gefüllt wird.
Ich habe einige zusätzliche Verbesserungen hinzugefügt, z. B. einen Abmeldeendpunkt, der ein Token ungültig macht, indem 401 zurückgegeben wird (das auch von Nginx zwischengespeichert wird), sodass das Token nicht mehr verwendet werden kann, wenn der Benutzer auf Abmelden klickt, auch wenn es nicht abgelaufen ist.
Außerdem enthält mein Nginx-Cache für jedes Token den zugeordneten Benutzer als JSON-Objekt, sodass ich ihn nicht aus der Datenbank abrufen kann, wenn ich diese Informationen benötige. und erspart mir auch das Entschlüsseln des Tokens.
Informationen zur Token-Lebensdauer und zum Aktualisieren von Token
Nach 5 Minuten ist das Token im Cache abgelaufen, sodass das Backend erneut abgefragt wird. Dies soll sicherstellen, dass Sie ein Token ungültig machen können, weil sich der Benutzer abmeldet, weil es kompromittiert wurde und so weiter. Durch eine solche regelmäßige erneute Validierung mit ordnungsgemäßer Implementierung im Backend muss ich keine Aktualisierungstoken verwenden.
Herkömmlicherweise werden Aktualisierungstoken verwendet, um ein neues Zugriffstoken anzufordern. Sie werden in Ihrem Backend gespeichert und Sie überprüfen, ob eine Anforderung für ein Zugriffstoken mit einem Aktualisierungstoken erfolgt, das mit dem in der Datenbank für diesen bestimmten Benutzer übereinstimmt. Wenn sich der Benutzer abmeldet oder Token gefährdet sind, löschen / ungültig machen Sie das Aktualisierungstoken in Ihrer Datenbank, sodass die nächste Anforderung eines neuen Tokens mit dem ungültig gemachten Aktualisierungstoken fehlschlägt.
Kurz gesagt, Aktualisierungstoken haben normalerweise eine lange Gültigkeit und werden immer mit dem Backend verglichen. Sie werden verwendet, um Zugriffstoken mit einer sehr kurzen Gültigkeit (einige Minuten) zu generieren. Diese Zugriffstoken erreichen normalerweise Ihr Backend, aber Sie überprüfen nur deren Signatur und Ablaufdatum.
Hier in meinem Setup verwenden wir Token mit einer längeren Gültigkeit (kann Stunden oder ein Tag sein), die dieselbe Rolle und dieselben Funktionen haben wie ein Zugriffstoken und ein Aktualisierungstoken. Da die Validierung und Invalidierung von Nginx zwischengespeichert wird, werden sie vom Backend nur einmal alle 5 Minuten vollständig überprüft. So behalten wir den Vorteil der Verwendung von Aktualisierungstoken (um ein Token schnell ungültig zu machen) ohne die zusätzliche Komplexität. Und eine einfache Validierung erreicht niemals Ihr Backend, das mindestens eine Größenordnung langsamer ist als der Nginx-Cache, selbst wenn es nur zur Überprüfung der Signatur und des Ablaufdatums verwendet wird.
Mit diesem Setup konnte ich die Authentifizierung in meinem Backend deaktivieren, da alle eingehenden Anforderungen die auth_request
Nginx-Direktive erreichen, bevor sie berührt werden.
Damit ist das Problem nicht vollständig gelöst, wenn Sie eine Autorisierung pro Ressource durchführen müssen, aber zumindest den grundlegenden Autorisierungsteil gespeichert haben. Sie können sogar vermeiden, das Token zu entschlüsseln oder eine DB-Suche durchzuführen, um auf Token-Daten zuzugreifen, da die zwischengespeicherte Nginx-Authentifizierungsantwort Daten enthalten und an das Backend zurückgeben kann.
Jetzt ist meine größte Sorge, dass ich etwas Offensichtliches in Bezug auf Sicherheit brechen kann, ohne es zu merken. Davon abgesehen wird jedes empfangene Token noch mindestens einmal validiert, bevor es von Nginx zwischengespeichert wird. Jedes temperierte Token wäre anders, würde also nicht in den Cache gelangen, da auch der Cache-Schlüssel anders wäre.
Vielleicht ist es auch erwähnenswert, dass eine Authentifizierung in der realen Welt gegen das Stehlen von Token kämpfen würde, indem eine zusätzliche Nonce oder etwas anderes generiert (und überprüft) wird.
Hier ist ein vereinfachter Auszug meiner Nginx-Konfiguration für meine App:
# Cache for internal auth checks
proxy_cache_path /usr/local/var/nginx/cache/auth levels=1:2 keys_zone=auth_cache:10m max_size=128m inactive=10m use_temp_path=off;
# Cache for content
proxy_cache_path /usr/local/var/nginx/cache/resx levels=1:2 keys_zone=content_cache:16m max_size=128m inactive=5m use_temp_path=off;
server {
listen 443 ssl http2;
server_name ........;
include /usr/local/etc/nginx/include-auth-internal.conf;
location /api/v1 {
# Auth magic happens here
auth_request /auth;
auth_request_set $user $upstream_http_X_User_Id;
auth_request_set $customer $upstream_http_X_Customer_Id;
auth_request_set $permissions $upstream_http_X_Permissions;
# The backend app, once Nginx has performed internal auth.
proxy_pass http://127.0.0.1:5000;
proxy_set_header X-User-Id $user;
proxy_set_header X-Customer-Id $customer;
proxy_set_header X-Permissions $permissions;
# Cache content
proxy_cache content_cache;
proxy_cache_key "$request_method-$request_uri";
}
location /api/v1/Logout {
auth_request /auth/logout;
}
}
Hier ist der Konfigurationsextrakt für den internen /auth
Endpunkt, der oben aufgeführt ist als /usr/local/etc/nginx/include-auth-internal.conf
:
# Called before every request to backend
location = /auth {
internal;
proxy_cache auth_cache;
proxy_cache_methods GET HEAD POST;
proxy_cache_key "$cookie_token";
# Valid tokens cache duration is set by backend returning a properly set Cache-Control header
# Invalid tokens are shortly cached to protect backend but not flood Nginx cache
proxy_cache_valid 401 30s;
# Valid tokens are cached for 5 minutes so we can get the backend to re-validate them from time to time
proxy_cache_valid 200 5m;
proxy_pass http://127.0.0.1:1234/auth/_Internal;
proxy_set_header Host ........;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header Accept application/json;
}
# To invalidate a not expired token, use a specific backend endpoint.
# Then we cache the token invalid/401 response itself.
location = /auth/logout {
internal;
proxy_cache auth_cache;
proxy_cache_key "$cookie_token";
# Proper caching duration (> token expire date) set by backend, which will override below default duration
proxy_cache_valid 401 30m;
# A Logout requests forces a cache refresh in order to store a 401 where there was previously a valid authorization
proxy_cache_bypass 1;
# This backend endpoint always returns 401, with a cache header set to the expire date of the token
proxy_pass http://127.0.0.1:1234/auth/_Internal/Logout;
proxy_set_header Host ........;
proxy_pass_request_body off;
}
.
Adressierung von Inhalten
Jetzt wird die Authentifizierung von den Daten getrennt. Da Sie angegeben haben, dass es für jeden Benutzer identisch ist, kann der Inhalt selbst auch von Nginx zwischengespeichert werden (in meinem Beispiel in der content_cache
Zone).
Skalierbarkeit
Dieses Szenario funktioniert sofort, vorausgesetzt, Sie haben einen Nginx-Server. In einem realen Szenario haben Sie wahrscheinlich eine hohe Verfügbarkeit, dh mehrere Nginx-Instanzen, die möglicherweise auch Ihre (Laravel-) Backend-Anwendung hosten. In diesem Fall kann jede Anfrage Ihrer Benutzer an einen Ihrer Nginx-Server gesendet werden. Bis alle das Token lokal zwischengespeichert haben, erreichen sie Ihr Backend, um es zu überprüfen. Für eine kleine Anzahl von Servern würde die Verwendung dieser Lösung immer noch große Vorteile bringen.
Es ist jedoch wichtig zu beachten, dass Sie bei mehreren Nginx-Servern (und damit Caches) die Möglichkeit verlieren, sich auf der Serverseite abzumelden, da Sie den Token-Cache nicht auf allen löschen können (indem Sie eine Aktualisierung erzwingen) /auth/logout
tut in meinem Beispiel. Sie haben nur noch die 5-Minuten-Token-Cache-Dauer, die dazu führt, dass Ihr Backend bald abgefragt wird, und Nginx mitteilt, dass die Anforderung abgelehnt wird. Eine teilweise Problemumgehung besteht darin, den Token-Header oder das Cookie auf dem Client beim Abmelden zu löschen.
Jeder Kommentar wäre sehr willkommen und dankbar!