Как я запутываю идентификаторы моих записей в рельсах?
Я пытаюсь понять, как обфускать идентификаторы моих записей в рельсах.
Например: типичный путь может выглядеть как http://domain/records/1, поэтому людям довольно легко определить, сколько трафика получает сайт если они просто создают новую запись.
Одним из решений, которое я использовал, является хэш-идентификатор с солью, но поскольку я не уверен, является ли эта функция биективной, я в конечном итоге сохраняю ее в другом столбце в моей базе данных и дважды проверяю на уникальность.
Другой вариант, о котором я думал, - генерировать случайный хэш и хранить его как другой столбец. Если это не уникально... просто сгенерируйте еще один.
Какой лучший способ сделать это?
Ответы
Ответ 1
Вы можете использовать встроенную библиотеку OpenSSL для шифрования и расшифровки ваших идентификаторов, поэтому вам нужно будет только перезаписать to_param
на ваших моделях. Вам также понадобится использовать Base64 для преобразования зашифрованных данных в обычный текст. Я бы придерживался этого в модуле, чтобы его можно было повторно использовать:
require 'openssl'
require 'base64'
module Obfuscate
def self.included(base)
base.extend self
end
def cipher
OpenSSL::Cipher::Cipher.new('aes-256-cbc')
end
def cipher_key
'blah!'
end
def decrypt(value)
c = cipher.decrypt
c.key = Digest::SHA256.digest(cipher_key)
c.update(Base64.decode64(value.to_s)) + c.final
end
def encrypt(value)
c = cipher.encrypt
c.key = Digest::SHA256.digest(cipher_key)
Base64.encode64(c.update(value.to_s) + c.final)
end
end
Итак, теперь ваши модели должны выглядеть примерно так:
class MyModel < ActiveRecord::Base
include Obfuscate
def to_param
encrypt id
end
end
Затем в вашем контроллере, когда вам нужно найти запись по зашифрованному идентификатору, вы должны использовать что-то вроде этого:
MyModel.find MyModel.decrypt(params[:id])
Если вы хотите шифровать/расшифровывать идентификаторы без, сохраняя их в базе данных, это, вероятно, самый простой способ.
Ответ 2
Вместо числовых идентификаторов используйте какой-то дружественный URL-адрес или читаемый пользователем пул. В этом отделе есть множество инструментов. Мало того, что они более дружелюбны к вашим пользователям, но хорошо выбранные слизни могут дать хорошее преимущество в поисковых системах.
Ответ 3
После прочтения @siannopollo post я создал Gem, основанный на идее его сообщения (но с некоторыми улучшениями): https://github.com/pencil/encrypted_id
Ответ 4
Здесь драгоценный камень, который держит его числовым, не требует миграции базы данных и никаких изменений маршрутизации: https://github.com/namick/obfuscate_id
Я обнаружил, что этот драгоценный камень не работает в согласии с некоторыми другими драгоценными камнями, в частности paper_trail. Это связано с тем, как он заменяет метод find
, а paper_trail вызывает вызов find
с фактическим идентификатором записи.
Итак, я использовал функциональность gam "scatter_swap", но не остальную часть. Здесь модель:
require 'obfuscate_id/scatter_swap'
class Page < ActiveRecord::Base
# This is a random number that, if changed, will invalidate all existing URLs. Don't change it!
@@obfuscate_spin = # random number here, which is essentially the encryption key
##
# Generate URL parameter to be used in the URL as the "id"
def to_param
# Use the obfuscate_id gem class to "spin" the id into something obfuscated
spun_id = ScatterSwap.hash(self.id, @@obfuscate_spin)
# Throw any additional attributes in here that are to be included in the URL.
"#{spun_id} #{name}".parameterize
end
def self.find_by_slug!(slug)
spun_id = slug[/^[0-9]+/]
begin
find_by_id! ScatterSwap.reverse_hash(spun_id, @@obfuscate_spin)
rescue ActiveRecord::RecordNotFound => e
raise ActiveRecord::RecordNotFound, "Couldn't find matching Page."
end
end
end
И в контроллере:
class PagesController < InheritedResources::Base
# Find the page using its URL slug
before_filter :find_page, except: [:index, :create, :new]
def find_page
@page = Page.find_by_slug! params[:id]
# If the URL doesn't match exactly, and this is a GET.
# We'll redirect to the new, correct URL, but if this is a non-GET, let let them finish their request instead.
if params[:id] != @page.to_param && request.get?
redirect_to url_for({ id: @page.to_param }), status: 301
end
end
end
В качестве альтернативы переадресации, которая имеет место там, вы можете просто указать канонический URL-адрес на странице. В перенаправлении есть ошибка игнорирования любых параметров запроса в URL-адресе. Это не было проблемой для моего проекта, так как у меня его не было. Но канонический URL-адрес будет лучше.
Ответ 5
Очень легко создавать уникальные случайные идентификаторы для ваших записей либо с использованием рандомизированного генератора строк, либо с помощью простого вызова Digest::SHA1.hexdigest
, который дает разумно случайные и криптографически уникальные результаты.
Например, вы можете создать дополнительный столбец с именем ident
или unique_id
, который хранит ваши общедоступные идентификаторы. Затем вы можете переписать to_param
вместо этого:
class MyModel < ActiveRecord::Base
before_create :assign_ident
def self.from_param(ident)
find_by_ident(ident)
end
def to_param
self.ident
end
protected
def assign_ident
self.ident = Digest::SHA1.hexdigest(SecureRandom.random_number(1<<256).to_s)
end
end
Теоретически существует вероятность столкновения на SHA1, но шансы настолько астрономически низки, что вы можете столкнуться с программным сбоем из-за ошибки памяти или сбоя оборудования. Вы можете проверить это, чтобы узнать, подходит ли оно вашим потребностям, создав несколько миллиардов идентификаторов, чтобы увидеть, столкнулись ли они когда-либо, чего они не должны. 256-битное случайное число должно обеспечивать достаточный объем данных для алгоритма SHA1 для пережевывания.