У меня есть веб-проект, который позволяет пользователям работать как в сети, так и в автономном режиме, и я ищу способ создания уникальных идентификаторов для записей на стороне клиента. Я хотел бы, чтобы подход работал, когда пользователь находится в автономном режиме (то есть не может общаться с сервером), гарантированно уникален и безопасен. Под «безопасным» я особенно беспокоюсь, что клиенты отправляют дублирующиеся идентификаторы (злонамеренно или иным образом) и тем самым наносят ущерб целостности данных.
Я занимался поиском, надеясь, что это уже решенная проблема. Я не нашел ничего определенного, особенно с точки зрения подходов, используемых в производственных системах. Я нашел несколько примеров для систем, где пользователи будут иметь доступ только к тем данным, которые они создали (например, список Todo, доступ к которому осуществляется на нескольких устройствах, но только тем, кто их создал). К сожалению, мне нужно что-то более сложное. Я нашел некоторые действительно хорошие идеи здесь , которые в соответствии с тем, как я думал , вещи могли бы работать.
Ниже мое предлагаемое решение.
Некоторые требования
- Идентификаторы должны быть глобально уникальными (или, по крайней мере, уникальными в системе)
- Сгенерировано на клиенте (т.е. с помощью JavaScript в браузере)
- Безопасный (как указано выше и в других случаях)
- Данные могут быть просмотрены / отредактированы несколькими пользователями, включая пользователей, которые их не создавали
- Не вызывает значительных проблем с производительностью для db базы данных (таких как MongoDB или CouchDB)
Предложенное решение
Когда пользователи создают учетную запись, они получают uuid, который был сгенерирован сервером и известен как уникальный в системе. Этот идентификатор НЕ должен совпадать с токеном аутентификации пользователя. Давайте назовем этот идентификатор пользователя «id token».
Когда пользователь создает новую запись, он генерирует новый uuid в javascript (генерируется с использованием window.crypto, когда доступно. См. Примеры здесь ). Этот идентификатор объединяется с идентификатором, который пользователь получил при создании своей учетной записи. Этот новый составной идентификатор (токен идентификатора на стороне сервера + uuid на стороне клиента) теперь является уникальным идентификатором записи. Когда пользователь подключен к сети и отправляет эту новую запись на внутренний сервер, сервер должен:
- Определите это как действие «вставка» (т.е. не обновление или удаление)
- Проверьте правильность обеих частей составного ключа
- Проверьте, что предоставленная часть «id token» составного идентификатора верна для текущего пользователя (то есть соответствует идентификатору токена, назначенному пользователю сервером при создании его учетной записи)
- Если все Copasetic, вставка данных в БД (соблюдая осторожность , чтобы сделать вставку , а не «upsert» , так что если идентификатор делает уже существует , он не обновляет существующую запись по ошибке)
Запросы, обновления и удаления не требуют специальной логики. Они просто использовали бы идентификатор для записи так же, как традиционные приложения.
Каковы преимущества этого подхода?
Код клиента может создавать новые данные в автономном режиме и сразу же узнавать идентификатор этой записи. Я рассмотрел альтернативные подходы, когда на клиенте будет создан временный идентификатор, который впоследствии будет заменен на «окончательный» идентификатор, когда система будет в сети. Тем не менее, это чувствовало себя очень хрупким. Особенно, когда вы начинаете думать о создании дочерних данных с внешними ключами, которые также необходимо обновить. Не говоря уже о работе с URL, которые будут меняться при изменении идентификатора.
Делая идентификаторы составными из сгенерированного клиентом значения И сгенерированного сервером значения, каждый пользователь эффективно создает идентификаторы в песочнице. Это предназначено для ограничения ущерба, который может быть нанесен злонамеренным / мошенническим клиентом. Кроме того, любые конфликты идентификаторов происходят для каждого пользователя, а не для всей системы.
Поскольку маркер идентификатора пользователя привязан к его учетной записи, идентификаторы могут создаваться в изолированной программной среде пользователя только клиентами, которые прошли проверку подлинности (т. Е. Там, где пользователь успешно вошел в систему). Это сделано для того, чтобы не дать злонамеренным клиентам создавать плохие идентификаторы для пользователя. Конечно, если маркер авторизации пользователя был украден злонамеренным клиентом, он может совершать плохие действия. Но, как только маркер аутентификации был украден, учетная запись все равно подвергается риску. В случае, если это произошло, нанесенный ущерб будет ограничен скомпрометированной учетной записью (а не всей системой).
Обеспокоенность
Вот некоторые из моих проблем с этим подходом
Будет ли это генерировать достаточно уникальные идентификаторы для крупномасштабного приложения? Есть ли основания полагать, что это приведет к конфликтам идентификаторов? Может ли javascript генерировать достаточно случайный uuid для этого? Похоже, window.crypto довольно широко доступен, и для этого проекта уже требуются достаточно современные браузеры. ( этот вопрос теперь имеет отдельный вопрос SO )
Есть ли какие-то лазейки, которые я пропускаю, которые могут позволить злоумышленнику взломать систему?
Есть ли причина беспокоиться о производительности БД при запросе составного ключа, состоящего из 2 uuids. Как сохранить этот идентификатор для лучшей производительности? Два отдельных поля или одно поле объекта? Будет ли другой «лучший» подход для Mongo vs Couch? Я знаю, что непоследовательный первичный ключ может вызвать заметные проблемы с производительностью при выполнении вставок. Было бы разумнее иметь автоматически сгенерированное значение для первичного ключа и хранить этот идентификатор в виде отдельного поля? ( этот вопрос теперь имеет отдельный вопрос SO )
С помощью этой стратегии было бы легко определить, что конкретный набор записей был создан одним и тем же пользователем (поскольку все они использовали один и тот же общедоступный токен id). Хотя я не вижу никаких непосредственных проблем с этим, всегда лучше не пропускать больше информации о внутренних деталях, чем необходимо. Другой возможностью было бы хеширование составного ключа, но кажется, что это может быть больше проблем, чем оно того стоит.
В случае коллизии идентификатора для пользователя, не существует простого способа восстановления. Я предполагаю, что клиент может сгенерировать новый идентификатор, но это похоже на большую работу для крайнего случая, который действительно никогда не должен происходить. Я собираюсь оставить это без внимания.
Только аутентифицированные пользователи могут просматривать и / или редактировать данные. Это приемлемое ограничение для моей системы.
Заключение
Выше разумного плана? Я понимаю, что отчасти это сводится к суждению, основанному на более полном понимании рассматриваемого приложения.