Стратегия создания уникальных и безопасных идентификаторов для использования в «иногда автономном» веб-приложении

47

У меня есть веб-проект, который позволяет пользователям работать как в сети, так и в автономном режиме, и я ищу способ создания уникальных идентификаторов для записей на стороне клиента. Я хотел бы, чтобы подход работал, когда пользователь находится в автономном режиме (то есть не может общаться с сервером), гарантированно уникален и безопасен. Под «безопасным» я особенно беспокоюсь, что клиенты отправляют дублирующиеся идентификаторы (злонамеренно или иным образом) и тем самым наносят ущерб целостности данных.

Я занимался поиском, надеясь, что это уже решенная проблема. Я не нашел ничего определенного, особенно с точки зрения подходов, используемых в производственных системах. Я нашел несколько примеров для систем, где пользователи будут иметь доступ только к тем данным, которые они создали (например, список Todo, доступ к которому осуществляется на нескольких устройствах, но только тем, кто их создал). К сожалению, мне нужно что-то более сложное. Я нашел некоторые действительно хорошие идеи здесь , которые в соответствии с тем, как я думал , вещи могли бы работать.

Ниже мое предлагаемое решение.

Некоторые требования

  1. Идентификаторы должны быть глобально уникальными (или, по крайней мере, уникальными в системе)
  2. Сгенерировано на клиенте (т.е. с помощью JavaScript в браузере)
  3. Безопасный (как указано выше и в других случаях)
  4. Данные могут быть просмотрены / отредактированы несколькими пользователями, включая пользователей, которые их не создавали
  5. Не вызывает значительных проблем с производительностью для db базы данных (таких как MongoDB или CouchDB)

Предложенное решение

Когда пользователи создают учетную запись, они получают uuid, который был сгенерирован сервером и известен как уникальный в системе. Этот идентификатор НЕ должен совпадать с токеном аутентификации пользователя. Давайте назовем этот идентификатор пользователя «id token».

Когда пользователь создает новую запись, он генерирует новый uuid в javascript (генерируется с использованием window.crypto, когда доступно. См. Примеры здесь ). Этот идентификатор объединяется с идентификатором, который пользователь получил при создании своей учетной записи. Этот новый составной идентификатор (токен идентификатора на стороне сервера + uuid на стороне клиента) теперь является уникальным идентификатором записи. Когда пользователь подключен к сети и отправляет эту новую запись на внутренний сервер, сервер должен:

  1. Определите это как действие «вставка» (т.е. не обновление или удаление)
  2. Проверьте правильность обеих частей составного ключа
  3. Проверьте, что предоставленная часть «id token» составного идентификатора верна для текущего пользователя (то есть соответствует идентификатору токена, назначенному пользователю сервером при создании его учетной записи)
  4. Если все Copasetic, вставка данных в БД (соблюдая осторожность , чтобы сделать вставку , а не «upsert» , так что если идентификатор делает уже существует , он не обновляет существующую запись по ошибке)

Запросы, обновления и удаления не требуют специальной логики. Они просто использовали бы идентификатор для записи так же, как традиционные приложения.

Каковы преимущества этого подхода?

  1. Код клиента может создавать новые данные в автономном режиме и сразу же узнавать идентификатор этой записи. Я рассмотрел альтернативные подходы, когда на клиенте будет создан временный идентификатор, который впоследствии будет заменен на «окончательный» идентификатор, когда система будет в сети. Тем не менее, это чувствовало себя очень хрупким. Особенно, когда вы начинаете думать о создании дочерних данных с внешними ключами, которые также необходимо обновить. Не говоря уже о работе с URL, которые будут меняться при изменении идентификатора.

  2. Делая идентификаторы составными из сгенерированного клиентом значения И сгенерированного сервером значения, каждый пользователь эффективно создает идентификаторы в песочнице. Это предназначено для ограничения ущерба, который может быть нанесен злонамеренным / мошенническим клиентом. Кроме того, любые конфликты идентификаторов происходят для каждого пользователя, а не для всей системы.

  3. Поскольку маркер идентификатора пользователя привязан к его учетной записи, идентификаторы могут создаваться в изолированной программной среде пользователя только клиентами, которые прошли проверку подлинности (т. Е. Там, где пользователь успешно вошел в систему). Это сделано для того, чтобы не дать злонамеренным клиентам создавать плохие идентификаторы для пользователя. Конечно, если маркер авторизации пользователя был украден злонамеренным клиентом, он может совершать плохие действия. Но, как только маркер аутентификации был украден, учетная запись все равно подвергается риску. В случае, если это произошло, нанесенный ущерб будет ограничен скомпрометированной учетной записью (а не всей системой).

Обеспокоенность

Вот некоторые из моих проблем с этим подходом

  1. Будет ли это генерировать достаточно уникальные идентификаторы для крупномасштабного приложения? Есть ли основания полагать, что это приведет к конфликтам идентификаторов? Может ли javascript генерировать достаточно случайный uuid для этого? Похоже, window.crypto довольно широко доступен, и для этого проекта уже требуются достаточно современные браузеры. ( этот вопрос теперь имеет отдельный вопрос SO )

  2. Есть ли какие-то лазейки, которые я пропускаю, которые могут позволить злоумышленнику взломать систему?

  3. Есть ли причина беспокоиться о производительности БД при запросе составного ключа, состоящего из 2 uuids. Как сохранить этот идентификатор для лучшей производительности? Два отдельных поля или одно поле объекта? Будет ли другой «лучший» подход для Mongo vs Couch? Я знаю, что непоследовательный первичный ключ может вызвать заметные проблемы с производительностью при выполнении вставок. Было бы разумнее иметь автоматически сгенерированное значение для первичного ключа и хранить этот идентификатор в виде отдельного поля? ( этот вопрос теперь имеет отдельный вопрос SO )

  4. С помощью этой стратегии было бы легко определить, что конкретный набор записей был создан одним и тем же пользователем (поскольку все они использовали один и тот же общедоступный токен id). Хотя я не вижу никаких непосредственных проблем с этим, всегда лучше не пропускать больше информации о внутренних деталях, чем необходимо. Другой возможностью было бы хеширование составного ключа, но кажется, что это может быть больше проблем, чем оно того стоит.

  5. В случае коллизии идентификатора для пользователя, не существует простого способа восстановления. Я предполагаю, что клиент может сгенерировать новый идентификатор, но это похоже на большую работу для крайнего случая, который действительно никогда не должен происходить. Я собираюсь оставить это без внимания.

  6. Только аутентифицированные пользователи могут просматривать и / или редактировать данные. Это приемлемое ограничение для моей системы.

Заключение

Выше разумного плана? Я понимаю, что отчасти это сводится к суждению, основанному на более полном понимании рассматриваемого приложения.

herbrandson
источник
Я думаю, что этот вопрос может заинтересовать вас stackoverflow.com/questions/105034/… Также это читается мне как GUID, но они не кажутся родными в javascript
Rémi
2
Меня поражает, что UUID уже удовлетворяют 5 перечисленным требованиям. Почему они недостаточны?
Гейб
@Gabe Смотрите мои комментарии к лжи Райана пост ниже
herbrandson
мета обсуждение этого вопроса: meta.stackoverflow.com/questions/251215/…
gnat
"злонамеренный / румянский клиент" - мошенник.
Дэвид Конрад

Ответы:

4

Ваш подход будет работать. Многие системы управления документами используют этот тип подхода.

Следует учитывать, что вам не нужно использовать как пользовательский uuid, так и случайный идентификатор элемента как часть строки. Вместо этого вы можете хешировать объединение обоих. Это даст вам более короткий идентификатор и, возможно, некоторые другие преимущества, поскольку результирующие идентификаторы будут распределены более равномерно (лучше сбалансированы для индексации и хранения файлов, если вы храните файлы на основе их uuid).

Другой вариант - создать временный uuid для каждого элемента. Затем, когда вы подключаетесь и отправляете их на сервер, сервер генерирует (гарантированные) значения uuid для каждого элемента и возвращает их вам. Затем вы обновите свою локальную копию.

GrandmasterB
источник
2
Я рассмотрел использование хэша 2 в качестве идентификатора. Тем не менее, мне не показалось, что есть подходящий способ генерировать sha256 во всех браузерах, которые мне нужно поддерживать :(
herbrandson
12

Вы должны разделить две проблемы:

  1. Генерация идентификатора: клиент должен иметь возможность генерировать уникальный идентификатор в распределенной системе
  2. Проблема безопасности: клиент ДОЛЖЕН иметь действительный токен аутентификации пользователя И токен аутентификации действителен для создаваемого / модифицируемого объекта

Решение этих двух, к сожалению, является отдельным; но, к счастью, они не являются несовместимыми.

Забота о генерации идентификатора легко решается путем генерации с использованием UUID, для чего и предназначен UUID; однако из соображений безопасности потребуется, чтобы вы проверили на сервере, что данный токен аутентификации авторизован для операции (т. е. если токен аутентификации предназначен для пользователя, которому не принадлежат необходимые разрешения для этого конкретного объекта, то он ДОЛЖЕН быть отвергается).

При правильной обработке коллизия не создаст проблемы безопасности (пользователя или клиента просто попросят повторить операцию с другим UUID).

Ли Райан
источник
Это действительно хороший момент. Возможно, это все, что требуется, и я думаю об этом. Тем не менее, у меня есть несколько опасений по поводу этого подхода. Самым большим является то, что сгенерированные javascript uuids кажутся не такими случайными, как можно было бы надеяться (подробности о задержаниях см. В комментариях на stackoverflow.com/a/2117523/13181 ). Кажется, что window.crypto должен решить эту проблему, но, похоже, это не доступно во всех версиях браузера, которые мне нужно поддерживать.
Хербрандсон
продолжение ... Мне нравится ваше предложение добавить код в клиенте, который восстановит новый uuid в случае коллизии. Однако мне кажется, что это вновь отражает обеспокоенность, с которой я столкнулся в своем посте по пункту № 1 раздела «Каковы преимущества этого подхода». Я думаю, что если бы я пошел по этому пути, мне лучше было бы просто создать временные идентификаторы на стороне клиента и затем обновить их с помощью «окончательного идентификатора» на сервере после подключения
herbrandson
Продолжение снова ... Более того, предоставление пользователям возможности отправлять свои собственные уникальные идентификаторы кажется проблемой безопасности. Возможно, размера uuid и высокой статистической вероятности столкновения достаточно, чтобы смягчить эту проблему сами по себе. Я не уверен. У меня есть неприятное подозрение, что держать каждого пользователя в своей «песочнице» - это просто хорошая идея в этом случае (то есть не доверять вводу пользователя).
Хербрандсон
@herbrandson: я не могу думать о проблеме безопасности, позволяющей пользователям создавать свои собственные уникальные идентификаторы, если вы всегда проверяете, есть ли у пользователя разрешения на эту операцию. Идентификатор - это просто то, что может быть использовано для идентификации объектов, на самом деле не имеет значения, какова его ценность. Единственный потенциальный вред состоит в том, что пользователь может зарезервировать диапазон идентификаторов для своего собственного использования, но это на самом деле не представляет никакой проблемы для системы в целом, поскольку другие пользователи также вряд ли могут получить эти значения случайно.
Ли Райан
Спасибо за ваш отзыв. Это действительно заставило меня прояснить мои мысли! Есть причина, по которой я остерегался твоего подхода, и я забыл об этом по пути :). Мой страх связан с плохим RNG во многих браузерах. Для генерации uuid предпочтительнее криптографически сильный ГСЧ. Многие новые браузеры имеют это через window.crypto, но старые браузеры этого не делают. Вследствие этого злоумышленник может выяснить, где RNG других пользователей получен, и, таким образом, узнать следующий uuid, который будет сгенерирован. Это та часть, которая чувствует, что на нее можно напасть.
Гербрандсон