web-dev-qa-db-fra.com

Stratégie pour générer des identifiants uniques et sécurisés à utiliser dans une application Web "parfois hors ligne"

J'ai un projet basé sur le Web qui permet aux utilisateurs de travailler à la fois en ligne et hors ligne et je cherche un moyen de générer des identifiants uniques pour les enregistrements côté client. J'aimerais une approche qui fonctionne pendant qu'un utilisateur est hors ligne (c'est-à-dire incapable de parler à un serveur), est garantie unique et sécurisée. Par "sécurisé", je m'inquiète spécifiquement des clients soumettant des identifiants en double (par malveillance ou autrement) et causant ainsi des ravages sur l'intégrité des données.

J'ai fait des recherches sur Google, en espérant que ce problème était déjà résolu. Je n'ai rien trouvé de très définitif, surtout en termes d'approches utilisées dans les systèmes de production. J'ai trouvé quelques exemples de systèmes où les utilisateurs n'accéderont qu'aux données qu'ils ont créées (par exemple, une liste Todo accessible sur plusieurs appareils, mais uniquement par l'utilisateur qui l'a créée). Malheureusement, j'ai besoin de quelque chose d'un peu plus sophistiqué. J'ai trouvé de très bonnes idées ici , qui correspondent à la façon dont je pensais que les choses pourraient fonctionner.

Voici ma solution proposée.

Quelques exigences

  1. Les identifiants doivent être globalement uniques (ou au moins uniques dans le système)
  2. Généré sur le client (c'est-à-dire via javascript dans le navigateur)
  3. Sécurisé (comme indiqué ci-dessus et autrement)
  4. Les données peuvent être consultées/modifiées par plusieurs utilisateurs, y compris des utilisateurs qui ne les ont pas créés
  5. Ne cause pas de problèmes de performances significatifs pour les db backend (tels que MongoDB ou CouchDB)

Solution proposée

Lorsque les utilisateurs créent un compte, ils reçoivent un uuid généré par le serveur et connu pour être unique au sein du système. Cet identifiant ne doit PAS être le même que le jeton d'authentification des utilisateurs. Appelons cet identifiant les utilisateurs "jeton d'identification".

Lorsqu'un utilisateur crée un nouvel enregistrement, il génère un nouvel uuid en javascript (généré à l'aide de window.crypto lorsqu'il est disponible. Voir les exemples ici ). Cet identifiant est concaténé avec le "jeton d'identification" que l'utilisateur a reçu lors de la création de son compte. Ce nouvel identifiant composite (jeton id côté serveur + uuid côté client) est désormais l'identifiant unique de l'enregistrement. Lorsque l'utilisateur est en ligne et soumet ce nouvel enregistrement au serveur principal, le serveur:

  1. Identifiez cela comme une action "insérer" (c'est-à-dire pas une mise à jour ou une suppression)
  2. Valider les deux parties de la clé composite sont des uuids valides
  3. Validez que la partie "jeton d'ID" fournie de l'ID composite est correcte pour l'utilisateur actuel (c'est-à-dire qu'elle correspond au jeton d'ID que le serveur a attribué à l'utilisateur lors de la création de son compte)
  4. Si tout est copasétique, insérez les données dans la base de données (en faisant attention à faire une insertion et non un "upsert" de sorte que si l'id existe existe déjà) il ne met pas à jour un enregistrement existant par erreur)

Les requêtes, mises à jour et suppressions ne nécessiteraient aucune logique particulière. Ils utiliseraient simplement l'identifiant de l'enregistrement de la même manière que les applications traditionnelles.

Quels sont les avantages de cette approche?

  1. Le code client peut créer de nouvelles données hors ligne et connaître immédiatement l'identifiant de cet enregistrement. J'ai considéré des approches alternatives où un identifiant temporaire serait généré sur le client qui serait plus tard échangé contre un identifiant "final" lorsque le système était en ligne. Cependant, cela semblait très fragile. Surtout lorsque vous commencez à penser à créer des données enfants avec des clés étrangères qui devraient également être mises à jour. Sans parler du traitement des URL qui changeraient lorsque l'identifiant changerait.

  2. En faisant des identifiants un composite d'une valeur générée par le client ET d'une valeur générée par le serveur, chaque utilisateur crée efficacement des identifiants dans un bac à sable. Ceci est destiné à limiter les dommages qui peuvent être causés par un client malveillant/escroc. En outre, toutes les collisions d'ID se font par utilisateur, et ne sont pas globales pour l'ensemble du système.

  3. Étant donné qu'un jeton d'ID utilisateur est lié à son compte, les ID ne peuvent être générés dans un sandbox d'utilisateurs que par des clients authentifiés (c'est-à-dire là où l'utilisateur s'est connecté avec succès). Ceci est destiné à empêcher les clients malveillants de créer de mauvais identifiants pour un utilisateur. Bien sûr, si un jeton d'authentification d'utilisateur était volé par un client malveillant, il pourrait faire de mauvaises choses. Mais, une fois qu'un jeton d'authentification a été volé, le compte est de toute façon compromis. Dans le cas où cela se produirait, les dommages causés seraient limités au compte compromis (pas à l'ensemble du système).

Préoccupations

Voici certaines de mes préoccupations avec cette approche

  1. Cela générera-t-il des identifiants suffisamment uniques pour une application à grande échelle? Y a-t-il une raison de penser que cela entraînera des collisions d'identité? Javascript peut-il générer un uuid suffisamment aléatoire pour que cela fonctionne? Il semble que window.crypto soit assez largement disponible et ce projet nécessite déjà des navigateurs raisonnablement modernes. ( cette question a maintenant une SO question propre )

  2. Y a-t-il des failles qui me manquent qui pourraient permettre à un utilisateur malveillant de compromettre le système?

  3. Y a-t-il lieu de s'inquiéter des performances de la base de données lors de la requête d'une clé composite composée de 2 uuids. Comment cet identifiant doit-il être stocké pour de meilleures performances? Deux champs séparés ou un seul champ d'objet? Y aurait-il une "meilleure" approche différente pour Mongo vs Couch? Je sais qu'avoir une clé primaire non séquentielle peut entraîner des problèmes de performances notables lors des insertions. Serait-il plus intelligent d'avoir une valeur générée automatiquement pour la clé primaire et de stocker cet identifiant dans un champ séparé? ( cette question a maintenant une SO question propre )

  4. Avec cette stratégie, il serait facile de déterminer qu'un ensemble particulier d'enregistrements a été créé par le même utilisateur (car ils partageraient tous le même jeton d'identification visible publiquement). Bien que je ne vois aucun problème immédiat avec cela, il est toujours préférable de ne pas divulguer plus d'informations sur les détails internes que nécessaire. Une autre possibilité serait de hacher la clé composite, mais cela semble être plus problématique que cela ne vaut.

  5. En cas de collision d'ID pour un utilisateur, il n'y a pas de moyen simple de récupérer. Je suppose que le client pourrait générer un nouvel identifiant, mais cela semble beaucoup de travail pour un cas Edge qui ne devrait vraiment jamais se produire. J'ai l'intention de laisser cela sans réponse.

  6. Seuls les utilisateurs authentifiés peuvent afficher et/ou modifier des données. Il s'agit d'une limitation acceptable pour mon système.

Conclusion

Est-ce au-dessus d'un plan raisonnable? Je sais que cela se résume en partie à un jugement fondé sur une meilleure compréhension de l'application en question.

47
herbrandson

Votre approche fonctionnera. De nombreux systèmes de gestion de documents utilisent ce type d'approche.

Une chose à considérer est que vous n'avez pas besoin d'utiliser à la fois l'uuid utilisateur et l'identifiant d'élément aléatoire dans la chaîne. Vous pouvez à la place hacher la concatination des deux. Cela vous donnera un identifiant plus court, et peut-être d'autres avantages, car les identifiants résultants seront distribués plus uniformément (mieux équilibrés pour l'indexation et le stockage de fichiers si vous stockez des fichiers en fonction de leur uuid).

Vous pouvez également générer un uuid temporaire pour chaque élément. Ensuite, lorsque vous vous connectez et les publiez sur le serveur, le serveur génère des uuid (garantis) pour chaque élément et vous les renvoie. Vous mettez ensuite à jour votre copie locale.

4
GrandmasterB

Vous devez séparer les deux préoccupations:

  1. Génération d'ID: le client doit pouvoir générer un identifiant unique dans un système distribué
  2. Problème de sécurité: le client DOIT avoir un jeton d'authentification utilisateur valide ET le jeton d'authentification est valide pour l'objet en cours de création/modification

Malheureusement, la solution à ces deux problèmes est distincte; mais heureusement, ils ne sont pas incompatibles.

Le problème de la génération d'ID est facilement résolu en générant avec UUID, c'est à cela que l'UUID est conçu; le problème de sécurité nécessiterait cependant que vous vérifiiez sur le serveur que le jeton d'authentification donné est autorisé pour l'opération (c'est-à-dire si le jeton d'authentification est pour un utilisateur qui ne possède pas l'autorisation nécessaire sur cet objet particulier, alors il DOIT être rejeté).

Lorsqu'elle est gérée correctement, la collision ne pose pas vraiment de problème de sécurité (l'utilisateur ou le client sera simplement invité à réessayer l'opération avec un autre UUID).

12
Lie Ryan