Strategia per la generazione di identificatori univoci e sicuri da utilizzare in un'app Web "a volte offline"


47

Ho un progetto basato sul Web che consente agli utenti di lavorare sia online che offline e sto cercando un modo per generare ID univoci per i record sul lato client. Vorrei un approccio che funzioni mentre un utente è offline (cioè incapace di parlare con un server), è garantito per essere unico ed è sicuro. Per "sicuro", sono particolarmente preoccupato per i clienti che inviano ID duplicati (maliziosi o meno) e quindi causano il caos sull'integrità dei dati.

Ho cercato su Google, sperando che questo fosse già un problema risolto. Non ho trovato nulla di molto definitivo, soprattutto in termini di approcci in uso nei sistemi di produzione. Ho trovato alcuni esempi di sistemi in cui gli utenti accederanno solo ai dati che hanno creato (ad esempio un elenco Todo a cui si accede su più dispositivi, ma solo dall'utente che lo ha creato). Sfortunatamente, ho bisogno di qualcosa di un po 'più sofisticato. Qui ho trovato alcune idee davvero valide , in linea con il modo in cui pensavo che le cose potessero funzionare.

Di seguito è la mia soluzione proposta.

Alcuni requisiti

  1. Gli ID dovrebbero essere univoci a livello globale (o almeno univoci all'interno del sistema)
  2. Generato sul client (ovvero tramite javascript nel browser)
  3. Sicuro (come indicato sopra e altrimenti)
  4. I dati possono essere visualizzati / modificati da più utenti, inclusi gli utenti che non li hanno creati
  5. Non causa significativi problemi di prestazioni per i database back-end (come MongoDB o CouchDB)

La soluzione proposta

Quando gli utenti creano un account, viene loro assegnato un uuid che è stato generato dal server e noto per essere unico nel sistema. Questo ID NON deve essere uguale al token di autenticazione degli utenti. Chiamiamo questo id "token ID" dell'utente.

Quando un utente crea un nuovo record, genera un nuovo uuid in javascript (generato usando window.crypto quando disponibile. Vedi esempi qui ). Questo ID è concatenato con il "token ID" ricevuto dall'utente quando ha creato il proprio account. Questo nuovo ID composito (token ID lato server + uuid lato client) è ora l'identificatore univoco per il record. Quando l'utente è online e invia questo nuovo record al server back-end, il server dovrebbe:

  1. Identificalo come un'azione "inserisci" (ovvero non un aggiornamento o una cancellazione)
  2. Convalida entrambe le parti della chiave composita sono uuidi validi
  3. Convalida che la parte "token ID" fornita dell'ID composito sia corretta per l'utente corrente (ovvero corrisponde al token ID assegnato dal server all'utente quando ha creato il proprio account)
  4. Se tutto è copasetico, inserisci i dati nel db (facendo attenzione a fare un inserimento e non un "upsert" in modo che se l'id esiste già non aggiorni un record esistente per errore)

Query, aggiornamenti ed eliminazioni non richiederebbero alcuna logica speciale. Userebbero semplicemente l'id per il record allo stesso modo delle applicazioni tradizionali.

Quali sono i vantaggi di questo approccio?

  1. Il codice client può creare nuovi dati offline e conoscere immediatamente l'id per quel record. Ho preso in considerazione approcci alternativi in ​​cui un ID temporaneo sarebbe stato generato sul client che sarebbe stato successivamente sostituito con un ID "finale" quando il sistema era online. Tuttavia, questo sembrava molto fragile. Soprattutto quando inizi a pensare alla creazione di dati figlio con chiavi esterne che dovrebbero anche essere aggiornate. Per non parlare della gestione degli URL che cambieranno quando l'ID cambierà.

  2. Rendendo gli ID un composto di un valore generato dal client E un valore generato dal server, ogni utente sta effettivamente creando ID in un sandbox. Questo ha lo scopo di limitare il danno che può essere fatto da un client malizioso / canaglia. Inoltre, eventuali collisioni ID sono per utente, non globali per l'intero sistema.

  3. Poiché un token ID utente è associato al proprio account, gli ID possono essere generati nella sandbox di utenti solo da client autenticati (ovvero dove l'utente ha effettuato correttamente l'accesso). Questo ha lo scopo di impedire ai client malintenzionati di creare ID errati per un utente. Naturalmente, se un token di autenticazione dell'utente è stato rubato da un client dannoso, potrebbero fare cose cattive. Ma, una volta rubato un token di autenticazione, l'account viene comunque compromesso. Nel caso in cui ciò accadesse, il danno arrecato sarebbe limitato all'account compromesso (non all'intero sistema).

preoccupazioni

Ecco alcune delle mie preoccupazioni su questo approccio

  1. Questo genererà ID sufficientemente unici per un'applicazione su larga scala? C'è qualche motivo per pensare che ciò comporterà collisioni id? Javascript può generare un uuid sufficientemente casuale perché funzioni? Sembra che window.crypto sia abbastanza ampiamente disponibile e questo progetto richiede già browser ragionevolmente moderni. ( questa domanda ora ha una sua domanda SO separata )

  2. Ci sono delle lacune che mi mancano che potrebbero consentire a un utente malintenzionato di compromettere il sistema?

  3. C'è motivo di preoccuparsi delle prestazioni del DB quando si esegue una query per una chiave composita composta da 2 uuidi. Come deve essere archiviato questo ID per le migliori prestazioni? Due campi separati o un singolo campo oggetto? Ci sarebbe un diverso approccio "migliore" per Mongo vs Couch? So che avere una chiave primaria non sequenziale può causare notevoli problemi di prestazioni durante gli inserimenti. Sarebbe più intelligente avere un valore generato automaticamente per la chiave primaria e memorizzare questo ID come campo separato? ( questa domanda ora ha una sua domanda SO separata )

  4. Con questa strategia, sarebbe facile determinare che un determinato set di record è stato creato dallo stesso utente (poiché condividono tutti lo stesso token ID visibile pubblicamente). Anche se non vedo alcun problema immediato con questo, è sempre meglio non perdere più informazioni sui dettagli interni di quanto sia necessario. Un'altra possibilità sarebbe quella di eseguire l'hashing della chiave composita, ma sembra che potrebbe essere più un problema di quanto valga la pena.

  5. Nel caso in cui vi sia una collisione id per un utente, non esiste un modo semplice per ripristinare. Suppongo che il client potrebbe generare un nuovo ID, ma questo sembra un sacco di lavoro per un caso limite che in realtà non dovrebbe mai accadere. Ho intenzione di lasciare questo senza indirizzo.

  6. Solo gli utenti autenticati possono visualizzare e / o modificare i dati. Questa è una limitazione accettabile per il mio sistema.

Conclusione

È al di sopra di un piano ragionevole? Mi rendo conto che parte di questo si riduce a una sentenza basata su una comprensione più completa dell'applicazione in questione.


Penso che questa domanda possa interessarti stackoverflow.com/questions/105034/… Anche questa lettura mi piace come GUID ma non sembrano essere nativi in ​​javascript
Rémi

2
Mi sembra che gli UUID soddisfino già i 5 requisiti elencati. Perché sono insufficienti?
Gabe,

@Gabe Vedi i miei commenti sul post di lie ryans di seguito
Herbrandson,

meta discussione su questa domanda: meta.stackoverflow.com/questions/251215/...
moscerino

"client dannoso / rouge" - canaglia.
David Conrad,

Risposte:


4

Il tuo approccio funzionerà. Molti sistemi di gestione dei documenti utilizzano questo tipo di approccio.

Una cosa da considerare è che non è necessario utilizzare sia l'UUID utente che l'ID elemento casuale come parte della stringa. Puoi invece eseguire l'hash della concatinazione di entrambi. Questo ti darà un identificatore più breve e forse alcuni altri vantaggi perché l'id risultante sarà distribuito in modo più uniforme (meglio bilanciato per l'indicizzazione e l'archiviazione dei file se stai archiviando i file in base al loro uuid).

Un'altra opzione che hai è quella di generare solo un uuid temporaneo per ogni elemento. Quindi quando ti connetti e li pubblichi sul server, il server genera gli uuid (garantiti) per ogni articolo e te li restituisce. Quindi aggiorni la tua copia locale.


2
Avevo considerato di usare un hash del 2 come ID. Tuttavia, non mi è sembrato che ci fosse un modo adatto per generare uno sha256 in tutti i browser che devo supportare :(
herbrandson

12

Devi separare le due preoccupazioni:

  1. Generazione ID: il client deve essere in grado di generare un identificatore univoco nel sistema distribuito
  2. Problema di sicurezza: il client DEVE avere un token di autenticazione utente valido E il token di autenticazione è valido per l'oggetto da creare / modificare

La soluzione a questi due purtroppo è separata; ma fortunatamente non sono incompatibili.

La preoccupazione per la generazione di ID viene facilmente risolta generando con UUID, per questo è progettato l'UUID; il problema di sicurezza richiederebbe tuttavia che si verifichi nel server che il token di autenticazione fornito sia autorizzato per l'operazione (ovvero se il token di autenticazione è per un utente che non possiede l'autorizzazione necessaria per quel particolare oggetto, DEVE essere respinto).

Se gestito correttamente, la collisione non costituirebbe realmente un problema di sicurezza (all'utente o al client verrà semplicemente chiesto di riprovare l'operazione con un altro UUID).


Questo è davvero un buon punto. Forse è tutto ciò che serve e lo sto pensando troppo. Tuttavia, ho alcune preoccupazioni su questo approccio. Il più grande è che gli uuidi generati da JavaScript non sembrano essere casuali come si potrebbe sperare (vedere i commenti su stackoverflow.com/a/2117523/13181 per le detenzioni). Sembra che window.crypto dovrebbe risolvere questo problema, ma questo non sembra essere disponibile in tutte le versioni di browser che devo supportare.
Herbrandson,

continua ... Mi piace il tuo suggerimento di aggiungere codice nel client che rigenererebbe un nuovo uuid in caso di collisione. Tuttavia, mi sembra che ciò reintroduca la preoccupazione che avevo nel mio post al punto 1 della sezione "Quali sono i vantaggi di questo approccio". Penso che se seguissi quella strada, starei meglio semplicemente generando ID temporanei sul lato client e quindi aggiornandoli con l '"ID finale" dal server una volta connesso
herbrandson

continua di nuovo ... Inoltre, consentire agli utenti di inviare il proprio ID univoco sembra essere un problema di sicurezza. Forse le dimensioni di un uuido e l'elevata improbabilità statistica di una collisione sono sufficienti per mitigare questa preoccupazione in sé e per sé. Non ne sono sicuro. Ho il sospetto fastidioso che tenere ogni utente nella propria "sandbox" sia solo una buona idea in questo caso (cioè non fidarsi di nessun input dell'utente).
Herbrandson,

@herbrandson: Non ci sono problemi di sicurezza che mi vengono in mente nel consentire agli utenti di generare il proprio ID univoco purché si controlli sempre che l'utente disponga delle autorizzazioni per l'operazione. L'ID è solo qualcosa che può essere usato per identificare gli oggetti, non importa quale sia il suo valore. L'unico danno potenziale è che l'utente può riservare una serie di ID per il proprio uso, ma ciò non pone alcun problema al sistema nel suo complesso perché è improbabile che altri utenti arrivino su tali valori solo per caso.
Lie Ryan,

Grazie per il tuo feedback Mi ha davvero costretto a chiarire il mio pensiero! C'è una ragione per cui ero diffidente nei confronti del tuo approccio e l'avevo dimenticato lungo la strada :). La mia paura è legata al povero RNG in molti browser. Per la generazione uuid, si preferirebbe un RNG crittograficamente forte. Molti browser più recenti hanno questo tramite window.crypto, ma i browser meno recenti no. A causa di ciò, è possibile per un utente malintenzionato capire il seme di un altro utente RNG e quindi conoscere il prossimo uuid che verrà generato. Questa è la parte che sembra possa essere attaccata.
Herbrandson,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.