UUID — это круто. Использование UUID в качестве первичного ключа в таблицах базы данных помогает защитить ваше приложение от атак с перечислением (или «угадыванием ресурсов»), и, если ваше приложение хранит данные в нескольких базах данных, это гарантирует, что ключ для данной записи (скажем, клиент) будет уникальным для всех из них.

Из-за того, как они генерируются, вероятность того, что два UUID будут одинаковыми, практически равна нулю.

Что такое UUID?

UUID (или «универсальный уникальный идентификатор») — это 128-битная строка (состоящая из 32 буквенно-цифровых символов, разделенных 4 тире), используемая для уникальной идентификации объекта или записи в вычислительной системе.

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

Преимущества UUID

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

Целочисленные первичные ключи удобны тем, что позволяют с первого взгляда определить порядок создания записей в базе данных (что упрощает отладку). Однако у этого есть один большой недостаток — последовательный характер целочисленных первичных ключей делает их легко угадываемыми.

Допустим, ваше приложение представляет собой интернет-магазин и позволяет покупателям просматривать статус своего заказа (вместе с информацией об адресе выставления счета и доставке) по следующему URL-адресу:

example.com/orders/123/status

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

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

Однако предположим, что вы должны были использовать UUID в качестве первичного ключа для таблицы заказов. В таком случае URL-адрес страницы статуса заказа будет выглядеть примерно так:

example.com/orders/12eb6b3f-b925-4e3d-bd41-78270530fb17/status

Для того подлого пользователя, который управляет магазином, который является вашим конкурентом, этот URL-адрес ничего не дает в отношении количества заказов, принятых вашим магазином. Это также не дает мошенническому пользователю простого способа «угадать» способ доступа к странице статуса для чужого заказа.

версии UUID

Хотя стандарт RFC 4122 определяет пять различных версий UUID, наиболее подходящими для создания действительно случайных UUID являются v1 и v4.

UUID v1

Генерация UUID версии 1 использует MAC-адрес генерирующего компьютера вместе с отметкой времени, что означает, что вероятность конфликта между двумя UUID практически равна нулю. Однако недостатком этого способа генерации является то, что UUID версии 1 можно «угадать».

UUID v4

UUID версии 4 генерируются полностью случайным образом (они не используют MAC-адрес и отметку времени при их генерации) и в результате более безопасны, чем версия 1. Однако недостатком является то, что существует (исчезающе) малая вероятность столкновения между двумя UUID.

UUID как первичные ключи в Postgres

За последние пару лет ROM стал нашим предпочтительным набором инструментов для персистентности в Icelab, так как вместе с Sequel он идеально подходит для стека на основе dry-rb, который мы теперь предпочитаем использовать при построении. сложные веб-приложения.

В недавнем проекте с использованием ROM я хотел использовать UUID в качестве первичного ключа для двух отношений («обсуждения» и «сообщения») с отношением has_many/belongs_to между ними. К счастью, учитывая, что ПЗУ может автоматически определять атрибуты UUID, этот процесс был довольно простым.

По умолчанию Postgres поддерживает хранение UUID, но не их создание. Для этого мне нужно было использовать расширение uuid-ossp Postgres, которое я добавил в БД при миграции следующим образом:

ROM::SQL.migration do
  up do
    execute <<-SQL
      CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
    SQL
  end

  down do
    execute <<-SQL
      DROP EXTENSION IF EXISTS "uuid-ossp";
    SQL
  end
end

Следующим шагом было добавление миграции для отношений «обсуждения» и «сообщения» и обеспечение автоматической генерации первичного ключа UUID для любых вновь вставленных записей.

Сначала отношение обсуждения:

create_table(:discussions) do
  column :id, :uuid, null: false, default: Sequel.function(:uuid_generate_v4)
  primary_key [:id]
  foreign_key :personal_user_id, :personal_users, null: false, index: true, on_delete: :cascade
  foreign_key :business_user_id, :business_users, null: false, index: true, on_delete: :cascade
end

Здесь мне нужно было определить столбец id как имеющий тип uuid, и использовать метод function Sequel для вызова функции uuid_generate_v4 расширения uuid-ossp для создания идентификатора для любых новых записей при вставке.

Тогда отношение сообщений:

create_table(:messages) do
  column :id, :uuid, null: false, default: Sequel.function(:uuid_generate_v4)
  primary_key [:id]
  foreign_key :discussion_id, :discussions, type: :uuid, null: false, index: true, on_delete: :cascade
  column :content, String, null: false
  column :sent_by, String, null: false
  column :status, String, null: false
end

Здесь я определил id, как и раньше, но мне также нужно было явно определить внешний ключ discussion_id как имеющий тип uuid.

И это все! Пути приложения теперь используют неисчислимые UUID в качестве идентификатора ресурса, при этом первичный ключ UUID автоматически генерируется для любой новой записи, когда она вставляется в базу данных.

Первоначально опубликовано на dylanwolff.com 19 июля 2018 г.