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 г.