Hintergrund:
Derzeit wird eine REST-API unter Verwendung des Knotens w / express erstellt, die von einer mobilen App und schließlich einer (modernen browserbasierten) Website verwendet wird.
Ich versuche herauszufinden, wie die Aktualisierungs- / Aktionsanforderung eines Benutzers am besten autorisiert werden kann, damit er nur seine eigenen Ressourcen ändern kann. Die Aktionen werden mit einer halbhohen Rate stattfinden, daher die Sorge.
Hinweis: Das Eigentum an Unternehmen kann in diesem Anwendungsfall nicht übertragen werden.
Potentielle Lösungen:
Lösung : Speichern und pflegen Sie eine Liste der Ressourcen jedes Benutzers in einer Serversitzung, die von reddis unterstützt wird.
Bedenken : Das Fortbestehen einer serverseitigen Sitzung hat ihre eigenen Komplexitäten, die speziell mit mehreren Servern skaliert werden können. Dies verstößt auch gegen den REST. Do-Sessions-wirklich-verletzen-Ruhe für weitere Informationen.
Lösung: Führen Sie vor der Update- / Aktionsabfrage des Benutzers eine Leseabfrage durch. IE geben mir die Elemente dieses Benutzers dann, wenn es in der Liste ist, mit dem Update fortfahren.
Bedenken: Overhead eines zusätzlichen Lesevorgangs jedes Mal, wenn ein Benutzer im Namen einer Ressource handelt.
Lösung: Übergeben Sie die Benutzer-ID an die Datenbankschicht und machen Sie sie Teil des Updates. Wenn Sie Lust haben, verwenden Sie für diese Ressource je nach Daten-Backend die Sicherheit auf Zeilenebene von Postgres.
Bedenken: Dies scheint etwas spät im Anforderungslebenszyklus zu sein, um zu überprüfen, ob die Ressource der anfordernde Benutzer ist. Der Fehler müsste vollständig aus dem Daten-Backend entfernt werden. Aus dem gleichen Grund wäre es auch etwas fehl am Platz, wenn man bedenkt, dass die Authentifizierung und rollenbasierte Autorisierung häufig zu Beginn des Anforderungslebenszyklus erfolgt. Die Implementierung ist auch vom Daten-Backend abhängig. Außerdem wird die Geschäftslogik in das Daten-Backend verschoben.
Lösung: Client-seitig signierte Sitzung. Entweder mit einem JWT oder einem verschlüsselten / signierten Cookie. Im Wesentlichen wird eine vertrauenswürdige Sitzung verwaltet, die eine Liste der Ressourcen-IDs des Benutzers enthält.
Bedenken: Die Größe der clientseitigen Sitzung. Die Tatsache, dass es mit jeder einzelnen Anfrage gesendet wird, auch wenn es nicht benötigt wird. Die Wartung wird äußerst komplex, wenn Sie die Möglichkeit mehrerer aktiver Sitzungen / Clients einführen. Wie würden Sie den clientseitigen Status aktualisieren, wenn eine Ressource auf einem anderen Client hinzugefügt wurde?
Lösung: Übergeben Sie beim Abrufen der Ressource ein signiertes Update-Token (JWT) oder eine URL an den Client mit der Ressource. Erwarten Sie dies, wenn eine Ressource aktualisiert / bearbeitet wird. Das signierte Token würde die Benutzer-ID und die Ressourcen-ID enthalten, und Sie könnten diese leicht überprüfen.
Bedenken: Wird komplex, wenn der Besitz von Ressourcen übertragbar ist, aber in meinem Fall ist dies kein Problem. Führt Komplexität beim Lesen vor dem Update ein. Es ist ein bisschen seltsam?
Abschließende Gedanken:
Ich neige zur letzten Lösung, aber weil ich es nicht sehr oft sehe, frage ich mich, ob mir etwas fehlt? oder vielleicht ist es Teil eines Designmusters, von dem ich nichts weiß.
quelle
Antworten:
Ich denke, Ihre Liste enthält zwei Lösungen, die am besten zu Ihrem Anwendungsfall passen. die Leseabfrage vor dem Update (Lösung 2) und das Senden eines Update-Tokens mit der Leseanforderung (Lösung 5).
Wenn Sie sich Sorgen um die Leistung machen, würde ich mich für die eine oder andere entscheiden, je nachdem, wie viele Lesevorgänge oder Aktualisierungen für eine Ressource Sie erwarten. Wenn Sie weit mehr Aktualisierungen als Lesevorgänge erwarten, ist Lösung 5 offensichtlich besser, da Sie keinen zusätzlichen Datenbankabruf durchführen müssen, um den Eigentümer der Ressource herauszufinden.
Sie sollten jedoch die Sicherheitsauswirkungen der Ausgabe eines Update-Tokens nicht vergessen. Wenn bei Lösung 2 die Authentifizierung sicher ist, ist das Aktualisieren einer Ressource wahrscheinlich auch sicher, da Sie den Eigentümer einer Ressource auf dem Server bestimmen.
Mit Lösung 5 überprüfen Sie die Behauptung des Kunden nicht noch einmal, außer dass Sie nach einer gültigen Signatur suchen. Wenn Sie das Update über einen Klartext-Link (z. B. kein SSL) durchführen lassen, ist es nicht sicher, nur die Ressource und die Benutzer-ID im Token zu codieren. Zum einen öffnen Sie sich für die Wiederholung von Angriffen, daher sollten Sie eine Nonce einfügen, die mit jeder Anforderung wächst. Wenn Sie im Update-Token keinen Zeitstempel / Ablaufdatum codieren, gewähren Sie grundsätzlich jedem, der über dieses Token verfügt, unbestimmten Update-Zugriff. Wenn Sie das Nonce nicht möchten, sollten Sie mindestens einen hmac der abgerufenen Ressource einschließen, damit das Aktualisierungstoken nur für den Status der abgerufenen Ressource gültig ist. Dies erschwert Wiederholungsangriffe und schränkt das Schadenswissen des Update-Tokens weiter ein.
Ich denke, selbst wenn Sie über eine sichere Verbindung kommunizieren, wäre es eine clevere Sache, eine Nonce (oder zumindest einen hmac des Status der Ressource) und ein Ablaufdatum für das Update-Token hinzuzufügen. Ich kenne die Anwendungsdomäne nicht, aber Sie können mit böswilligen Benutzern umgehen und sie können alle Arten von Chaos zerstören, da unbegrenzter Update-Zugriff auf ihre eigenen Ressourcen möglich ist.
Das Hinzufügen einer Nonce bedeutet eine zusätzliche Spalte pro Ressource in Ihrer Datenbank, in der Sie den Wert der Nonce speichern, die Sie mit der letzten Abrufanforderung gesendet haben. Sie können den hmac über die serialisierte JSON-Darstellung Ihrer Ressource berechnen. Sie können Ihr Token dann nicht als Teil des Nachrichtentexts, sondern als zusätzlichen HTTP-Header senden.
Oh, und ich würde ein Token verwenden, keine URL; In REST sollte die Aktualisierungs-URL für eine bestimmte Ressource dieselbe URL sein, von der die Ressource abgerufen wurde, oder? Ich würde alle authentifizierungs- und autorisierungsbezogenen Inhalte in die HTTP-Header verschieben, z. B. könnten Sie das Aktualisierungstoken im Autorisierungsheader der PUT-Anforderung bereitstellen.
Beachten Sie, dass für das Hinzufügen einer Nonce, die mit jedem Ressourcenabruf wächst, auch eine Datenbankaktualisierung (der neue Wert für die Nonce) für jede Ressourcenabrufanforderung erforderlich ist (obwohl Sie möglicherweise nur die Nonce aktualisieren müssen, wenn der Status der Ressource tatsächlich geändert wird Es wäre also vom Standpunkt der Leistung aus frei), es sei denn, Sie möchten diese Informationen im vorübergehenden Speicher behalten und die Clients einfach erneut versuchen lassen, wenn Ihr Server zwischen einem Abruf und einer Aktualisierungsanforderung neu gestartet wird.
Eine Randnotiz zu Ihrer Lösung 4 (clientseitige Sitzungen): Abhängig von der Anzahl der Ressourcen, die Ihre Benutzer erstellen können, ist die Sitzungsgröße möglicherweise kein Problem. Das Problem der clientseitigen Sitzungsaktualisierung ist möglicherweise auch recht einfach zu lösen: Wenn eine Aktualisierungsanforderung für eine Ressource fehlschlägt, weil die Ressource nicht in den Sitzungsdaten aufgeführt ist, die Sie von Ihrem Client erhalten haben, der Benutzer jedoch korrekt ist, führen Sie eine aus Überprüfen Sie im Backend, ob die Sitzungsdaten von diesem Client veraltet sind. Wenn dies der Fall ist, erlauben Sie das Update und senden ein aktualisiertes Cookie mit der Antwort auf die Update-Anfrage zurück. Dies führt nur dann zu einer Datenbanksuche, wenn ein Benutzer versucht, eine Ressource von einem Client mit veralteten lokalen Sitzungsdaten zu aktualisieren (oder wenn er böswillig ist und versucht, Sie zu unterstützen, die Rate jedoch auf die Rettung beschränkt!). Jedoch, Ich bin damit einverstanden, dass Lösung 4 komplizierter ist als die anderen und Sie mit einer Ihrer anderen Ideen besser zurechtkommen. Lösung 4 enthält auch verschiedene Sicherheitsaspekte, die Sie berücksichtigen sollten. Wenn Sie so viel Autorisierungsstatus auf dem Client speichern, muss Ihre Sicherheit wirklich wasserdicht sein.
quelle
Eine andere Lösung, die Sie in Betracht ziehen könnten:
Wenn eine Ressource nach ihrer Erstellung wirklich nicht auf einen anderen Benutzer übertragen werden kann, können Sie die Benutzer-ID in die Ressourcen-ID codieren.
Beispielsweise heißen alle Ressourcen, die dem Benutzer 'alice' gehören, 'alice-1', 'alice-2' usw.
Es ist dann trivial zu überprüfen, ob 'eve' Zugriff auf eine Ressource namens 'alice-1' hat.
Wenn Sie nicht möchten, dass der Ressourcenbesitz öffentlich bekannt ist, können Sie eine kryptografische Hash-Funktion wie folgt verwenden:
Generieren
hmac(password, u | '-' | r )
, wo | ist Verkettung. Verwenden Sie das Ergebnis zusammen mit dem Ressourcennamen r als ID der Ressource. Fügen Sie optional einen Salt-Wert hinzu (wie zum Speichern von Passwörtern).Wenn eine Aktualisierungsanforderung vom Benutzer 'alice' für eine bestimmte Ressourcen-ID kommt, extrahieren Sie r aus der Ressourcen-ID, berechnen hmac (Kennwort, 'alice' | '-' | r) und vergleichen es mit dem verbleibenden Teil der Ressourcen-ID . Wenn es übereinstimmt, darf Alice die Ressource aktualisieren. Wenn es nicht übereinstimmt, ist sie nicht autorisiert. Und ohne das Passwort kann niemand herausfinden, wem welche Ressource gehört.
Ich denke, das ist sicher, aber vielleicht möchten Sie die Qualitäten von hmacs auffrischen.
quelle
Es scheint zwei verschiedene Probleme zu geben, die Sie verwalten müssen. Sie sind (1) Wie authentifizieren Sie den Benutzer, der die Anfrage generiert? und (2) woher wissen Sie, welche Back-End-Objekte diesem Benutzer zugeordnet sind? Ich habe auch eine Beobachtung, die für Ihren Anwendungsfall geeignet sein könnte (3)
(1) Benutzerauthentifizierung auf REST-artige Weise ist niemals hübsch. Ich bevorzuge die Verwendung der HTTP-Authentifizierungs- / Autorisierungsheader und der HTTP 401-Antworten, um die Authentifizierung auszulösen. Dies bedeutet, dass Sie den Benutzer möglicherweise bei jeder Anforderung authentifizieren. Da es sich um eine mobile Anwendung handelt, haben Sie die Möglichkeit, ein eigenes benutzerdefiniertes Authentifizierungsschema zu erstellen, mit dem Sie in zukünftigen Anforderungen möglicherweise ein Sitzungstoken anstelle vollständiger Authentifizierungsdetails bereitstellen können.
(2) Der Benutzer, der einer Reihe von Objekten zugeordnet ist, ist sicherlich ein Schlüsseldaten, den Sie zusammen mit den tatsächlichen Objekten in Ihrem Datenspeicher speichern müssen. Als Teil Ihrer Geschäftslogik muss überprüft werden, wer der Eigentümer des Objekts ist, und es mit der Identität verglichen werden, die auf das Objekt zugreift. Ich würde dies explizit im Code tun, als Teil der Geschäftsschicht, die die Anfrage validiert. Wenn Datenbanktreffer ein ausreichend großes Problem darstellen, ist es sinnvoll, diese Informationen in einem Schlüsselwertspeicher wie Memcache oder (für sehr hochvolumige, hochverfügbare Websites, Riak) zwischenzuspeichern und die Datenbank nur zu treffen, wenn der Cache kalt ist für diese Benutzer / Objekt-Kombination.
(3) Wenn Sie eine ausreichend kleine Anzahl von Benutzern und Objekten haben, können Sie die Benutzer- und Objekt-ID in demselben Feld kombinieren und dieses Feld als Datenbankprimärschlüssel für die Objekttabelle verwenden. Beispiel: Die 64-Bit-Nummer bietet 24 Bit Platz für die Benutzer-ID und 40 Bit Platz für Objekt-IDs. Auf diese Weise können Sie bei der Abfrage des Objekts zu 100% sicher sein, dass es für Ihren Benutzer bestimmt ist.
quelle