Tengo un proyecto basado en la web que permite a los usuarios trabajar tanto en línea como fuera de línea y estoy buscando una forma de generar identificadores únicos para registros en el lado del cliente. Me gustaría un enfoque que funcione mientras un usuario está fuera de línea (es decir, no puede hablar con un servidor), se garantiza que sea único y seguro. Por "seguro", me preocupa específicamente que los clientes envíen identificaciones duplicadas (de forma maliciosa o de otro tipo) y, por lo tanto, causen estragos en la integridad de los datos.
He estado buscando en Google, esperando que esto ya fuera un problema resuelto. No he encontrado nada que sea muy definitivo, especialmente en términos de enfoques que se utilizan en los sistemas de producción. Encontré algunos ejemplos para sistemas donde los usuarios solo accederán a los datos que han creado (por ejemplo, una lista de Todo a la que se accede en múltiples dispositivos, pero solo por el usuario que la creó). Desafortunadamente, necesito algo un poco más sofisticado. Encontré algunas ideas realmente buenas aquí , que están en línea con la forma en que pensaba que las cosas podrían funcionar.
A continuación se muestra mi solución propuesta.
Algunos requisitos
- Las ID deben ser globalmente únicas (o al menos únicas dentro del sistema)
- Generado en el cliente (es decir, a través de JavaScript en el navegador)
- Seguro (como se describe anteriormente y de lo contrario)
- Los datos pueden ser vistos / editados por múltiples usuarios, incluidos los usuarios que no los crearon
- No causa problemas de rendimiento importantes para los backend db's (como MongoDB o CouchDB)
Solución propuesta
Cuando los usuarios crean una cuenta, recibirán un uuid que fue generado por el servidor y que se sabe que es único dentro del sistema. Este ID NO debe ser el mismo que el token de autenticación de usuarios. Llamemos a esta identificación los usuarios "token de identificación".
Cuando un usuario crea un nuevo registro, genera un nuevo uuid en javascript (generado usando window.crypto cuando está disponible. Vea ejemplos aquí ). Esta identificación se concatena con el "token de identificación" que recibió el usuario cuando creó su cuenta. Esta nueva identificación compuesta (token de identificación del lado del servidor + uuid del lado del cliente) ahora es el identificador único para el registro. Cuando el usuario está en línea y envía este nuevo registro al servidor de fondo, el servidor:
- Identifique esto como una acción de "inserción" (es decir, no una actualización o una eliminación)
- Validar ambas partes de la clave compuesta son uuids válidos
- Valide que la parte de "token de identificación" proporcionada de la identificación compuesta es correcta para el usuario actual (es decir, coincide con la ficha de identificación que el servidor asignó al usuario cuando creó su cuenta)
- Si todo está copasetic, insertar los datos en la base de datos (teniendo cuidado de hacer una inserción y no un "upsert" de modo que si el id hace ya existe no se actualiza un registro existente por error)
Las consultas, actualizaciones y eliminaciones no requerirían ninguna lógica especial. Simplemente usarían la identificación para el registro de la misma manera que las aplicaciones tradicionales.
¿Cuáles son las ventajas de este enfoque?
El código del cliente puede crear nuevos datos sin conexión y conocer la identificación de ese registro de inmediato. Consideré enfoques alternativos donde se generaría una identificación temporal en el cliente que luego se cambiaría por una identificación "final" cuando el sistema estaba en línea. Sin embargo, esto se sintió muy frágil. Especialmente cuando empiezas a pensar en crear datos secundarios con claves foráneas que también deberían actualizarse. Sin mencionar tratar con URL que cambiarían cuando cambiara la identificación.
Al hacer que los identificadores sean un compuesto de un valor generado por el cliente Y un valor generado por el servidor, cada usuario está creando efectivamente identificadores en un entorno limitado. Esto tiene la intención de limitar el daño que puede hacer un cliente malicioso / deshonesto. Además, cualquier colisión de identificación es por usuario, no global para todo el sistema.
Dado que el token de identificación de un usuario está vinculado a su cuenta, los identificadores solo pueden generar identificadores en un entorno limitado de usuarios autenticados (es decir, cuando el usuario inició sesión correctamente). Esto está destinado a evitar que los clientes malintencionados creen identificadores incorrectos para un usuario. Por supuesto, si un token de autenticación de usuarios fue robado por un cliente malintencionado, podrían hacer cosas malas. Pero, una vez que se ha robado un token de autenticación, la cuenta se ve comprometida de todos modos. En el caso de que esto sucediera, el daño causado se limitaría a la cuenta comprometida (no a todo el sistema).
Preocupaciones
Estas son algunas de mis preocupaciones con este enfoque.
¿Esto generará identificadores suficientemente únicos para una aplicación a gran escala? ¿Hay alguna razón para pensar que esto provocará colisiones de identificación? ¿Puede JavaScript generar un uuid suficientemente aleatorio para que esto funcione? Parece que window.crypto está bastante disponible y este proyecto ya requiere navegadores razonablemente modernos. ( esta pregunta ahora tiene una pregunta SO por separado )
¿Me faltan algunas lagunas que podrían permitir que un usuario malintencionado comprometa el sistema?
¿Hay alguna razón para preocuparse por el rendimiento de la base de datos al consultar una clave compuesta compuesta por 2 uuids? ¿Cómo se debe almacenar esta identificación para un mejor rendimiento? ¿Dos campos separados o un solo campo de objeto? ¿Habría un "mejor" enfoque diferente para Mongo vs Couch? Sé que tener una clave primaria no secuencial puede causar problemas notables de rendimiento al hacer inserciones. ¿Sería más inteligente tener un valor generado automáticamente para la clave primaria y almacenar esta identificación como un campo separado? ( esta pregunta ahora tiene una pregunta SO por separado )
Con esta estrategia, sería fácil determinar que un mismo conjunto de registros fue creado por el mismo usuario (ya que todos compartirían el mismo token de identificación visible públicamente). Si bien no veo ningún problema inmediato con esto, siempre es mejor no filtrar más información sobre detalles internos de la necesaria. Otra posibilidad sería hacer un hash de la clave compuesta, pero parece que puede ser más problemático de lo que vale.
En el caso de que haya una colisión de id para un usuario, no hay una manera simple de recuperarse. Supongo que el cliente podría generar una nueva identificación, pero esto parece mucho trabajo para un caso límite que realmente nunca debería suceder. Tengo la intención de dejar esto sin abordar.
Solo los usuarios autenticados pueden ver y / o editar datos. Esta es una limitación aceptable para mi sistema.
Conclusión
¿Está por encima de un plan razonable? Me doy cuenta de que parte de esto se reduce a una llamada de juicio basada en una comprensión más completa de la aplicación en cuestión.
fuente
Respuestas:
Tu enfoque funcionará. Muchos sistemas de gestión de documentos utilizan este tipo de enfoque.
Una cosa a tener en cuenta es que no necesita usar tanto el uuid del usuario como la identificación del elemento aleatorio como parte de la cadena. En su lugar, puede hacer hash la concatenación de ambos. Esto le dará un identificador más corto, y posiblemente otros beneficios, ya que los ID resultantes se distribuirán de manera más uniforme (mejor equilibrado para la indexación y almacenamiento de archivos si está almacenando archivos en función de su uuid).
Otra opción que tiene es generar solo un uuid temporal para cada elemento. Luego, cuando se conecta y los publica en el servidor, el servidor genera uuid (garantizado) para cada elemento y se lo devuelve. Luego actualiza su copia local.
fuente
Debe separar las dos preocupaciones:
La solución a estos dos, lamentablemente, está separada; pero afortunadamente no son incompatibles.
La preocupación sobre la generación de ID se resuelve fácilmente generando con UUID, para eso está diseñado UUID; Sin embargo, la preocupación de seguridad requeriría que verifique en el servidor que el token de autenticación dado está autorizado para la operación (es decir, si el token de autenticación es para un usuario que no posee el permiso necesario sobre ese objeto en particular, entonces DEBE ser rechazado).
Cuando se maneja correctamente, la colisión realmente no representaría un problema de seguridad (simplemente se le pedirá al usuario o al cliente que vuelva a intentar la operación con otro UUID).
fuente