Стратегія створення унікальних і безпечних ідентифікаторів для використання у веб-програмі "іноді офлайн"


47

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

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

Нижче моє запропоноване рішення.

Деякі вимоги

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

Пропоноване рішення

Коли користувачі створюють обліковий запис, їм буде надано uuid, який генерується сервером і, як відомо, є унікальним у системі. Цей ідентифікатор НЕ повинен бути таким же, як маркер автентифікації користувачів. Назвемо цей id користувачів "ідентифікатором ідентифікатора"

Коли користувач створює нову запис, вони генерують новий uuid у javascript (згенерований за допомогою window.crypto, коли він доступний. Див. Приклади тут ). Цей ідентифікатор поєднується з "маркером ідентифікатора", отриманим користувачем під час створення акаунта. Цей новий складений ідентифікатор (сторона ідентифікатора сторони сервера + uuid на стороні клієнта) тепер є унікальним ідентифікатором для запису. Коли користувач перебуває в Інтернеті і подає цю нову запис на сервер, який надає сервер, сервер:

  1. Ідентифікуйте це як дію "вставлення" (тобто не оновлення чи видалення)
  2. Перевірка обох частин складеного ключа є дійсними uuids
  3. Перевірте, що надана частина складеного ідентифікатора "маркер ідентифікатора" є правильною для поточного користувача (тобто вона відповідає маркеру ідентифікатора серверу, призначеному користувачеві під час створення облікового запису)
  4. Якщо все Copasetic, вставка даних в БД (дотримуючись обережності , щоб зробити вставку , а не «upsert» , так що якщо ідентифікатор робить вже існує , він не оновлює існуючу запис помилково)

Для запитів, оновлень та видалень не потрібна спеціальна логіка. Вони просто використовувати ідентифікатор для запису таким же чином, як і традиційні програми.

Які переваги такого підходу?

  1. Код клієнта може створювати нові дані в режимі офлайн і негайно знати ідентифікатор для цієї записи. Я розглядав альтернативні підходи, коли на клієнті буде створений тимчасовий ідентифікатор, який пізніше буде замінено на "остаточний" ідентифікатор, коли система була в мережі. Однак це було дуже крихким. Особливо, коли ви починаєте думати про створення дочірніх даних із зовнішніми ключами, які також потрібно було б оновити. Не кажучи вже про спілкування з URL-адресами, які змінюватимуться, коли змінювався би ідентифікатор.

  2. Здійснюючи ідентифікатори складеними клієнтським значенням І значенням, створеним сервером, кожен користувач ефективно створює ідентифікатори в пісочниці. Це призначено для обмеження шкоди, яку може нанести зловмисний / шахрайський клієнт. Крім того, будь-яке зіткнення id відбувається на основі кожного користувача, а не глобально для всієї системи.

  3. Оскільки маркер ідентифікатора користувачів прив’язаний до їх облікового запису, ідентифікатори можуть створюватись у пісочниці користувачів лише клієнтами, які мають автентифікацію (тобто там, де користувач успішно увійшов). Це покликане уберегти зловмисних клієнтів від створення поганих ідентифікаторів для користувача. Звичайно, якщо маркер авторів авторів був викрадений зловмисним клієнтом, вони можуть зробити погані речі. Але після того, як викрадений маркер авторства, рахунок все одно порушується. У випадку, якщо це сталося, завдана шкода буде обмежена компрометованим рахунком (не всією системою).

Побоювання

Ось деякі мої проблеми з таким підходом

  1. Чи створить це достатньо унікальний ідентифікатор для широкомасштабного застосування? Чи є підстави думати, що це призведе до зіткнення ідентифікаторів? Чи може JavaScript створити достатньо випадковий uuid для цього? Схоже, що window.crypto досить широко доступний, і цей проект вже потребує досить сучасних браузерів. ( це питання тепер має окреме запитання щодо ПП )

  2. Чи є якісь пропуски, які мені не вистачають, які могли б дозволити зловмисникові порушити систему?

  3. Чи є підстави турбуватися про продуктивність БД під час запитів на складений ключ, що складається з 2-х уідів. Як цей ідентифікатор повинен зберігатися для найкращої продуктивності? Два окремих поля або одне поле об’єкта? Чи був би інший "найкращий" підхід для Монго проти Куше? Я знаю, що наявність непослідовного первинного ключа може спричинити помітні проблеми з роботою при виконанні вставок. Чи було б розумніше мати автоматичне значення для основного ключа та зберегти цей ідентифікатор як окреме поле? ( це питання тепер має окреме запитання щодо ПП )

  4. За допомогою цієї стратегії було б легко визначити, що певний набір записів створювався тим самим користувачем (оскільки всі вони мають один і той же публічно видимий маркер id). Хоча я не бачу жодних негайних проблем із цим, завжди краще не просочувати більше інформації про внутрішні деталі, ніж це потрібно. Іншою можливістю може стати хеш-композитний ключ, але, схоже, це може бути більше проблем, ніж варто.

  5. У випадку, якщо користувач зіткнеться з ідентифікатором, не існує простого способу відновлення. Я думаю, що клієнт міг створити новий ідентифікатор, але це здається великою роботою для кращого випадку, який насправді ніколи не повинен відбуватися. Я маю намір залишити це без розгляду.

  6. Лише автентифіковані користувачі можуть переглядати та / або редагувати дані. Це прийнятне обмеження для моєї системи.

Висновок

Чи вище розумного плану? Я усвідомлюю, що щось із цього зводиться до виклику судового рішення, заснованого на більш повному розумінні розглянутої заявки.


Я думаю, що це питання може вас зацікавити stackoverflow.com/questions/105034/… Також мені це читалося як GUID, але вони, схоже, не є власними у JavaScript
Rémi

2
Мене вражає, що UUID вже відповідають 5 переліченим вимогам. Чому їх недостатньо?
Гейб

@Gabe Подивіться мої коментарі до публікації брехні ryans нижче
herbrandson

мета-обговорення цього питання: meta.stackoverflow.com/questions/251215/…
gnat

"зловмисний / rouge client" - шахрай.
Девід Конрад

Відповіді:


4

Ваш підхід спрацює. Багато систем управління документами використовують такий тип підходу.

Варто враховувати, що вам не потрібно використовувати як uuid користувача, так і ідентифікатор випадкового елемента в якості частини рядка. Натомість ви можете хешувати стислість обох. Це дасть вам коротший ідентифікатор і, можливо, деякі інші переваги, оскільки отриманий ідентифікатор буде більш рівномірно розподілений (краще збалансований для індексації та зберігання файлів, якщо ви зберігаєте файли на основі їх uuid).

Інший варіант, який ви маєте, - це генерувати лише тимчасовий uuid для кожного елемента. Потім, коли ви під’єднуєте та розміщуєте їх на сервері, сервер генерує (гарантовано) uuid для кожного елемента та повертає вам його. Потім ви оновлюєте свою локальну копію.


2
Я розглядав можливість використання хешу 2 як ідентифікатор. Однак мені не здалося, що є відповідний спосіб генерувати sha256 у всіх браузерах, які мені потрібно підтримувати :(
herbrandson

12

Вам потрібно розділити два питання:

  1. Генерація ідентифікаторів: клієнт повинен мати можливість генерувати унікальний ідентифікатор у розподіленій системі
  2. Побоювання безпеки: клієнт ДОЛЖЕН мати дійсний маркер аутентифікації користувача І маркер автентифікації дійсний для об'єкта, який створюється / змінюється

Рішення цих двох, на жаль, є окремими; але на щастя вони не несумісні.

Занепокоєння щодо генерації ідентифікаторів легко вирішується шляхом створення UUID, саме для цього призначений UUID; однак проблема безпеки потребує того, щоб ви перевірили на сервері, що даний маркер автентифікації дозволений для роботи (тобто, якщо маркер автентифікації призначений для користувача, який не володіє необхідним дозволом на конкретний об'єкт, то ОБОВ'ЯЗКОВО відхилено).

При правильному поводженні зіткнення насправді не спричинить проблеми безпеки (користувача або клієнта просто попросять повторити операцію з іншим UUID).


Це справді хороший момент. Можливо, це все, що потрібно, і я це переосмислюю. Однак у мене є кілька занепокоєнь щодо цього підходу. Найбільшим є те, що ууїди, що генеруються JavaScript, не здаються настільки випадковими, як можна сподіватися (див. Коментарі на stackoverflow.com/a/2117523/13181 для затриманих). Здається, що window.crypto повинен вирішити цю проблему, але це, здається, не доступне у всіх версіях браузера, які мені потрібно підтримувати.
herbrandson

продовження ... Мені подобається ваша пропозиція про додавання коду в клієнт, який міг би відновити новий uuid у разі зіткнення. Однак мені здається, що це знову викликає занепокоєння, яке було у мене на посаді під пунктом № 1 розділу "Які переваги такого підходу". Я думаю, що якби я пішов цим маршрутом, мені було б краще просто створити тимчасовий ідентифікатор на стороні клієнта, а потім оновити їх "остаточним ідентифікатором" з колись підключеного сервера
herbrandson

продовжується знову ... Більше того, дозволити користувачам подавати власні унікальні ідентифікатори, здається, щось із занепокоєння щодо безпеки. Можливо, розмір уюїду та висока статистична неможливість зіткнення є достатніми, щоб пом'якшити цю занепокоєння саме по собі. Я не впевнений. У мене виникає підозра, що тримати кожного користувача у його власній "пісочниці" - це лише гарна ідея в цьому випадку (тобто, довіряйте відсутності введення користувачем).
herbrandson

@herbrandson: Немає жодної проблеми із безпекою, яку я можу придумати, щоб дозволити користувачам генерувати власний унікальний ідентифікатор, якщо ви завжди перевіряєте, чи має користувач дозволи на операцію. Ідентифікатор - це лише те, що може бути використано для ідентифікації об'єктів, але насправді не має значення, яке його значення. Єдина потенційна шкода полягає в тому, що користувач може зарезервувати цілий спектр ідентифікаторів для власного використання, але це насправді не створює жодних проблем для системи в цілому, оскільки інші користувачі навряд чи зможуть досягти цих значень випадково.
Лі Лі Райан

Дякуємо за ваш відгук. Це дійсно змусило мене з'ясувати своє мислення! Є причина, що я насторожено ставлюся до вашого підходу, і я забув це по дорозі :). У багатьох браузерах мій страх пов’язаний із поганою RNG. Для uuid покоління варто віддати перевагу криптографічно сильному RNG. Багато нових браузерів мають це через window.crypto, але старі браузери цього не роблять. Завдяки цьому зловмисник може з'ясувати насіння інших користувачів RNG і тим самим знати наступний uuid, який буде генеруватися. Це та частина, яка відчуває, що на неї можна напасти.
herbrandson
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.