Strategie zur Generierung eindeutiger und sicherer Kennungen zur Verwendung in einer „manchmal offline“ Web-App

47

Ich habe ein webbasiertes Projekt, mit dem Benutzer sowohl online als auch offline arbeiten können, und ich suche nach einer Möglichkeit, eindeutige IDs für Datensätze auf der Clientseite zu generieren. Ich hätte gerne einen Ansatz, der funktioniert, wenn ein Benutzer offline ist (dh nicht mit einem Server kommunizieren kann), der garantiert eindeutig und sicher ist. Mit "sicher" befasse ich mich speziell mit Kunden, die doppelte IDs (böswillig oder auf andere Weise) übermitteln und dadurch die Datenintegrität in Mitleidenschaft ziehen.

Ich habe ein bisschen gegoogelt und gehofft, dass dies bereits ein gelöstes Problem war. Ich habe nichts Bestimmtes gefunden, insbesondere in Bezug auf Ansätze, die in Produktionssystemen verwendet werden. Ich habe einige Beispiele für Systeme gefunden, bei denen Benutzer nur auf die von ihnen erstellten Daten zugreifen (z. B. eine Aufgabenliste, auf die auf mehreren Geräten zugegriffen wird, jedoch nur von dem Benutzer, der sie erstellt hat). Leider brauche ich etwas ausgefeilteres. Ich habe hier ein paar wirklich gute Ideen gefunden , die meiner Meinung nach funktionieren könnten.

Unten ist meine vorgeschlagene Lösung.

Einige Anforderungen

  1. IDs sollten global eindeutig sein (oder zumindest innerhalb des Systems eindeutig sein)
  2. Auf dem Client generiert (zB über Javascript im Browser)
  3. Sicher (wie oben beschrieben und ansonsten)
  4. Daten können von mehreren Benutzern angezeigt / bearbeitet werden, auch von Benutzern, die sie nicht verfasst haben
  5. Verursacht keine signifikanten Leistungsprobleme für Backend-Datenbanken (wie MongoDB oder CouchDB)

Vorgeschlagene Lösung

Wenn Benutzer ein Konto erstellen, erhalten sie eine UUID, die vom Server generiert wurde und die im System eindeutig ist. Diese ID darf NICHT mit dem Authentifizierungstoken des Benutzers identisch sein. Nennen wir diese ID die Benutzer "ID-Token".

Wenn ein Benutzer einen neuen Datensatz erstellt, generiert er eine neue UUID in Javascript (wenn verfügbar mit window.crypto erstellt. Beispiele finden Sie hier ). Diese ID wird mit dem "ID-Token" verknüpft, das der Benutzer beim Erstellen seines Kontos erhalten hat. Diese neue zusammengesetzte ID (serverseitiges ID-Token + clientseitige UUID) ist jetzt die eindeutige ID für den Datensatz. Wenn der Benutzer online ist und diesen neuen Datensatz an den Back-End-Server sendet, führt der Server Folgendes aus:

  1. Identifizieren Sie dies als "Einfügen" -Aktion (dh keine Aktualisierung oder Löschung).
  2. Überprüfen Sie, ob beide Teile des zusammengesetzten Schlüssels gültige UUIDs sind
  3. Vergewissern Sie sich, dass der angegebene "ID-Token" -Teil der zusammengesetzten ID für den aktuellen Benutzer korrekt ist (dh er entspricht dem ID-Token, das der Server dem Benutzer beim Erstellen seines Kontos zugewiesen hat).
  4. Wenn alles copasetic ist, legen Sie die Daten in den db (wobei darauf geachtet , einen Einsatz und keine „Upsert“ zu tun , so dass , wenn die ID hat bereits existiert es nicht einen vorhandenen Datensatz versehentlich nicht aktualisiert)

Abfragen, Aktualisierungen und Löschvorgänge erfordern keine spezielle Logik. Sie würden einfach die ID für den Datensatz auf die gleiche Weise wie herkömmliche Anwendungen verwenden.

Was sind die Vorteile dieses Ansatzes?

  1. Der Client-Code kann offline neue Daten erstellen und kennt die ID für diesen Datensatz sofort. Ich habe alternative Ansätze in Betracht gezogen, bei denen auf dem Client eine temporäre ID generiert wird, die später gegen eine "endgültige" ID ausgetauscht wird, wenn das System online ist. Dies fühlte sich jedoch sehr spröde an. Insbesondere, wenn Sie darüber nachdenken, untergeordnete Daten mit Fremdschlüsseln zu erstellen, die ebenfalls aktualisiert werden müssten. Ganz zu schweigen vom Umgang mit URLs, die sich ändern würden, wenn sich die ID ändert.

  2. Indem IDs aus einem vom Client generierten Wert UND einem vom Server generierten Wert zusammengesetzt werden, erstellt jeder Benutzer effektiv IDs in einer Sandbox. Dies soll den Schaden begrenzen, den ein böswilliger / betrügerischer Client anrichten kann. Außerdem sind ID-Kollisionen benutzerbezogen und nicht global für das gesamte System.

  3. Da ein Benutzer-ID-Token an sein Konto gebunden ist, können IDs nur von Clients in einer Benutzersandbox generiert werden, die authentifiziert sind (dh bei denen sich der Benutzer erfolgreich angemeldet hat). Auf diese Weise sollen böswillige Clients daran gehindert werden, für einen Benutzer ungültige IDs zu erstellen. Wenn ein Benutzerauthentifizierungs-Token von einem böswilligen Client gestohlen wird, kann dies natürlich zu schlechten Ergebnissen führen. Sobald jedoch ein Authentifizierungs-Token gestohlen wurde, ist das Konto auf jeden Fall kompromittiert. In diesem Fall ist der verursachte Schaden auf das gefährdete Konto beschränkt (nicht auf das gesamte System).

Sorgen

Hier sind einige meiner Bedenken bezüglich dieses Ansatzes

  1. Generiert dies ausreichend eindeutige IDs für eine Großanwendung? Gibt es einen Grund zu der Annahme, dass dies zu ID-Kollisionen führt? Kann Javascript eine ausreichend zufällige UUID generieren, damit dies funktioniert? Es sieht so aus, als ob window.crypto ziemlich weit verbreitet ist und für dieses Projekt sind bereits einigermaßen moderne Browser erforderlich. ( Diese Frage hat jetzt eine eigene SO-Frage )

  2. Gibt es Lücken, die mir fehlen und die es einem böswilligen Benutzer ermöglichen könnten, das System zu gefährden?

  3. Gibt es Grund zur Besorgnis über die DB-Leistung, wenn Sie nach einem zusammengesetzten Schlüssel fragen, der aus 2 UUIDs besteht. Wie sollte diese ID gespeichert werden, um die bestmögliche Leistung zu erzielen? Zwei getrennte Felder oder ein einzelnes Objektfeld? Gibt es einen anderen "besten" Ansatz für Mongo vs Couch? Ich weiß, dass ein nicht sequentieller Primärschlüssel beim Einfügen erhebliche Leistungsprobleme verursachen kann. Wäre es sinnvoller, einen automatisch generierten Wert für den Primärschlüssel zu haben und diese ID als separates Feld zu speichern? ( Diese Frage hat jetzt eine eigene SO-Frage )

  4. Mit dieser Strategie lässt sich leicht feststellen, dass ein bestimmter Datensatzsatz von demselben Benutzer erstellt wurde (da alle denselben öffentlich sichtbaren ID-Token verwenden). Obwohl ich keine unmittelbaren Probleme damit sehe, ist es immer besser, nicht mehr Informationen über interne Details zu veröffentlichen, als benötigt werden. Eine andere Möglichkeit wäre das Hashing des zusammengesetzten Schlüssels, aber das scheint mehr Mühe zu bereiten, als es wert ist.

  5. Im Falle einer ID-Kollision für einen Benutzer gibt es keine einfache Möglichkeit zur Wiederherstellung. Ich nehme an, der Client könnte eine neue ID generieren, aber dies scheint eine Menge Arbeit für einen Edge-Fall zu sein, der eigentlich nie passieren sollte. Ich habe vor, dies unadressiert zu lassen.

  6. Nur authentifizierte Benutzer können Daten anzeigen und / oder bearbeiten. Dies ist eine akzeptable Einschränkung für mein System.

Fazit

Steht über einem vernünftigen Plan? Mir ist klar, dass ein Teil davon auf eine Entscheidung zurückzuführen ist, die auf einem umfassenderen Verständnis des betreffenden Antrags beruht.

Herbrandson
quelle
Ich denke, diese Frage könnte Sie interessieren stackoverflow.com/questions/105034/… Auch dies lesen Sie mir wie GUID, aber sie scheinen nicht in Javascript heimisch zu sein
Rémi
2
Es fällt mir auf, dass UUIDs bereits die 5 aufgeführten Anforderungen erfüllen. Warum sind sie nicht ausreichend?
Gabe
@Gabe Siehe meine Kommentare zu Lügen Ryan Post unten
Herbrandson
"bösartiger / roter Client" - Schurke.
David Conrad

Antworten:

4

Ihr Ansatz wird funktionieren. Viele Dokumentenverwaltungssysteme verwenden diesen Ansatz.

Eine zu berücksichtigende Sache ist, dass Sie nicht sowohl die Benutzer-UUID als auch die zufällige Element-ID als Teil der Zeichenfolge verwenden müssen. Sie können stattdessen die Concatination von beiden hashen. Dadurch erhalten Sie eine kürzere Kennung und möglicherweise einige weitere Vorteile, da die resultierenden IDs gleichmäßiger verteilt sind (ausgewogener für die Indizierung und die Dateispeicherung, wenn Sie Dateien basierend auf ihrer UUID speichern).

Sie haben auch die Möglichkeit, für jedes Element nur eine temporäre UUID zu generieren. Wenn Sie dann eine Verbindung herstellen und sie an den Server senden, generiert der Server (garantierte) UUIDs für jeden Artikel und gibt diese an Sie zurück. Anschließend aktualisieren Sie Ihre lokale Kopie.

GroßmeisterB
quelle
2
Ich hatte überlegt, einen Hash der 2 als ID zu verwenden. Es schien mir jedoch nicht möglich zu sein, in allen Browsern, die ich unterstützen muss, eine sha256 zu generieren :(
herbrandson 21.04.14
12

Sie müssen die beiden Anliegen trennen:

  1. ID-Generierung: Der Client muss in der Lage sein, eine eindeutige ID in einem verteilten System zu generieren
  2. Sicherheitsbedenken: Der Client MUSS ein gültiges Benutzerauthentifizierungstoken haben UND das Authentifizierungstoken ist für das Objekt gültig, das erstellt / geändert wird

Die Lösung für diese beiden sind leider getrennt; aber zum glück sind sie nicht inkompatibel.

Das Problem der ID-Generierung kann leicht gelöst werden, indem mit der UUID generiert wird, für die die UUID entwickelt wurde. Die Sicherheitsbedenken erfordern jedoch, dass Sie auf dem Server überprüfen, ob das angegebene Authentifizierungstoken für den Vorgang autorisiert ist (dh, wenn das Authentifizierungstoken für einen Benutzer bestimmt ist, der nicht über die erforderliche Berechtigung für dieses bestimmte Objekt verfügt, MUSS es sein abgelehnt).

Bei korrekter Behandlung würde eine Kollision kein wirkliches Sicherheitsproblem darstellen (der Benutzer oder der Client werden lediglich aufgefordert, den Vorgang mit einer anderen UUID zu wiederholen).

Lüge Ryan
quelle
Das ist ein wirklich guter Punkt. Vielleicht ist das alles, was benötigt wird und ich überdenke es. Ich habe jedoch einige Bedenken hinsichtlich dieses Ansatzes. Das größte Problem ist, dass die mit JavaScript generierten UUIDs nicht so zufällig zu sein scheinen, wie man vielleicht erhofft (siehe die Kommentare unter stackoverflow.com/a/2117523/13181 für die Rückstände). Es scheint, dass window.crypto dieses Problem beheben sollte, aber das scheint nicht in allen Browserversionen verfügbar zu sein, die ich unterstützen muss.
Herbrandson
Fortsetzung ... Ich mag Ihren Vorschlag, dem Client Code hinzuzufügen, der im Falle einer Kollision eine neue UUID generiert. Es scheint mir jedoch, dass dies die Bedenken, die ich in meinem Beitrag unter Punkt 1 des Abschnitts "Was sind die Vorteile dieses Ansatzes" hatte, wieder einführt. Ich denke, wenn ich diesen Weg gegangen wäre, wäre es besser, nur temporäre IDs auf der Client-Seite zu generieren und sie dann mit der "endgültigen ID" vom Server zu aktualisieren, sobald die Verbindung hergestellt ist
Herbrandson
Fortsetzung folgt erneut ... Darüber hinaus scheint es ein Sicherheitsrisiko zu sein, Benutzern das Einreichen eigener eindeutiger IDs zu ermöglichen. Vielleicht reichen die Größe einer UUID und die hohe statistische Unwahrscheinlichkeit einer Kollision aus, um diese Bedenken an und für sich abzumildern. Ich bin mir nicht sicher. Ich habe den lästigen Verdacht, dass es in diesem Fall nur eine gute Idee ist, jeden Benutzer in seiner eigenen "Sandbox" zu belassen (dh keine Benutzereingaben zu akzeptieren).
Herbrandson
@herbrandson: Es gibt keine Sicherheitsprobleme, die ich mir vorstellen kann, wenn ich zulasse, dass Benutzer ihre eigene eindeutige ID generieren, solange Sie immer überprüfen, ob der Benutzer über die Berechtigungen für den Vorgang verfügt. ID ist nur etwas, das zum Identifizieren von Objekten verwendet werden kann. Es spielt keine Rolle, welchen Wert sie haben. Der einzige potenzielle Schaden besteht darin, dass der Benutzer eine Reihe von IDs für den eigenen Gebrauch reservieren kann. Dies ist jedoch für das gesamte System kein wirkliches Problem, da es ebenso unwahrscheinlich ist, dass andere Benutzer nur zufällig auf diese Werte zugreifen.
Lie Ryan
Vielen Dank für Ihr Feedback. Es hat mich wirklich gezwungen, mein Denken zu klären! Es gibt einen Grund, warum ich mich vor deiner Herangehensweise fürchtete und es auf dem Weg vergessen hatte :). Meine Angst ist in vielen Browsern mit dem schlechten RNG verbunden. Für die UUID-Erzeugung würde man ein kryptografisch starkes RNG bevorzugen. Viele neuere Browser haben dies über window.crypto, ältere Browser jedoch nicht. Aufgrund dessen ist es einem böswilligen Benutzer möglich, den Keim eines anderen RNG-Benutzers herauszufinden und dadurch die nächste zu generierende UUID zu ermitteln. Dies ist der Teil, der sich angegriffen anfühlt.
Herbrandson