Ich arbeite an einer Web-App, die sich mit sehr großen Impulsen gleichzeitiger Benutzer befassen muss, die autorisiert werden müssen, um identische Inhalte anzufordern. In seinem aktuellen Zustand ist es sogar für eine 32-Kern-AWS-Instanz völlig lähmend.
(Beachten Sie, dass wir Nginx als Reverse-Proxy verwenden.)
Die Antwort kann nicht einfach zwischengespeichert werden, da wir im schlimmsten Fall überprüfen müssen, ob der Benutzer authentifiziert ist, indem wir seine JWT dekodieren. Dies erfordert, dass wir Laravel 4 starten , was die meisten zustimmen würden, und dass es langsam ist , selbst wenn PHP-FPM und OpCache aktiviert sind. Dies ist hauptsächlich auf die heftige Bootstrapping-Phase zurückzuführen.
Man könnte die Frage stellen: "Warum haben Sie PHP und Laravel überhaupt verwendet, wenn Sie wussten, dass dies ein Problem sein würde?" - aber jetzt ist es zu spät, um auf diese Entscheidung zurückzukommen!
Mögliche Lösung
Eine vorgeschlagene Lösung besteht darin, das Auth-Modul aus Laravel in ein leichtes externes Modul (in etwas Schnellem wie C geschrieben) zu extrahieren, dessen Aufgabe es ist, das JWT zu dekodieren und zu entscheiden, ob der Benutzer authentifiziert ist.
Der Ablauf einer Anfrage wäre:
- Überprüfen Sie, ob der Cache getroffen wurde (falls nicht wie gewohnt an PHP übergeben)
- Dekodiere den Token
- Überprüfen Sie, ob es gültig ist
- Wenn gültig , aus dem Cache bereitstellen
- Wenn dies ungültig ist , teilen Sie dies Nginx mit. Anschließend leitet Nginx die Anforderung an PHP weiter, um sie wie gewohnt zu behandeln.
Dies ermöglicht es uns, PHP nicht zu drücken, sobald wir diese Anfrage an einen einzelnen Benutzer gesendet haben, und stattdessen ein leichtes Modul zu erreichen, um mit der Dekodierung von JWTs und anderen Vorbehalten, die mit dieser Art von Authentifizierung verbunden sind, herumzuspielen.
Ich dachte sogar daran, diesen Code direkt als Nginx-HTTP-Erweiterungsmodul zu schreiben.
Sorgen
Ich mache mir Sorgen, dass ich das noch nie gesehen habe und mich gefragt habe, ob es einen besseren Weg gibt.
Wenn Sie der Seite benutzerspezifischen Inhalt hinzufügen, wird diese Methode vollständig beendet.
Gibt es eine andere einfachere Lösung direkt in Nginx? Oder müssten wir etwas Spezialisierteres wie Lack verwenden?
Meine Fragen:
Ist die obige Lösung sinnvoll?
Wie wird das normalerweise angegangen?
Gibt es einen besseren Weg, um einen ähnlichen oder besseren Leistungsgewinn zu erzielen?
quelle
Antworten:
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 eininternal
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 eineCache-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:
Hier ist der Konfigurationsextrakt für den internen
/auth
Endpunkt, der oben aufgeführt ist als/usr/local/etc/nginx/include-auth-internal.conf
:.
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!
quelle