Подтвердить что ты не робот

Создание уникального маркера на лету с Rails

Я хочу создать токен в моем контроллере для пользователя в столбце "user_info_token". Тем не менее, я хочу проверить, что в настоящее время у пользователя нет этого токена. Достаточно ли этого кода?

  begin
    @new_token = SecureRandom.urlsafe_base64 
    user = User.find_by_user_info_token(@new_token) 
  end while user != nil 

  @seller.user_info_token = @new_token 

Или существует ли более чистый способ сделать это?

4b9b3361

Ответ 1

Самое чистое решение, которое я нашел:

@seller.user_info_token = loop do
  token = SecureRandom.urlsafe_base64
  break token unless User.exists?(user_info_token: token)
end

И что-то очень чистое, но с потенциальными дубликатами (очень немногие):

@seller.user_info_token = SecureRandom.uuid

Случайная вероятность дублирования UUID

Изменить: конечно, добавьте уникальный индекс в ваш :user_info_token. Нам будет гораздо быстрее искать пользователя с тем же токеном и, если он по ошибке вызовет исключение, 2 пользователя будут сохранены в тот же момент с точно таким же токеном!

Ответ 2

Если ваш токен достаточно длинный и генерируется криптографически надежным генератором случайных чисел, тогда вы не должны убедиться, что токен уникален. Вы не должны генерировать токены в цикле.

16 исходных байтов достаточно долго для этой эффективной гарантии. При форматировании для безопасности URL результат будет длиннее.

# Base-64 (url-safe) encoded bytes, 22 characters long
SecureRandom.urlsafe_base64(16)

# Base-36 encoded bytes, naturally url-safe, ~25 characters long
SecureRandom.hex(16).to_i(16).to_s(36)

# Base-16 encoded bytes, naturally url-safe, 32 characters long
SecureRandom.hex(16)

Это связано с тем, что вероятность того, что 16-байтовый или 128-разрядный токен не является ununique, настолько исчезающе мала, что она практически равна нулю. Существует только 50% -ный шанс в случае повторений после приблизительно 2 64= 18 446 744 073 709 551 616 = 1,845 x 10 19. Если вы начнете генерировать один миллиард токенов в секунду, это займет примерно 2 64/(10 9 * 3600 * 24 * 365,25) = 600 лет, пока не будет 50% вероятность того, что произошли какие-либо повторения вообще.

Но вы не генерируете один миллиард токенов в секунду. Позвольте быть щедрым и предположим, что вы генерировали один токен в секунду. Временной интервал до 50% вероятности даже одного столкновения составляет 600 миллиардов лет. Планета будет поглощена солнцем задолго до этого.

Ответ 3

У меня много моделей, к которым я применяю уникальные маркеры. По этой причине я создал проблему Tokened в app/models/concerns/tokened.rb

module Tokened

  extend ActiveSupport::Concern

  included do
    after_initialize do
      self.token = generate_token if self.token.blank?
    end
  end

  private
    def generate_token
      loop do
        key = SecureRandom.base64(15).tr('+/=lIO0', 'pqrsxyz')
        break key unless self.class.find_by(token: key)
      end
    end
end

В любой модели я хочу иметь уникальные токены, я просто делаю

include Tokened

Но да, ваш код тоже выглядит отлично.

Ответ 4

Возможно, вы можете сделать что-то, используя фактическое время. Тогда вам не нужно будет проверять, был ли токен уже использован пользователем.

new_token = Digest::MD5.hexdigest(Time.now.to_i.to_s + rand(999999999).to_s)
user.user_info_token = new_token

Ответ 5

Rails 5 поставляется с этой функцией, вам нужно только добавить к вашей модели следующую строку:

class User
  has_secure_token
end

Поскольку Rails 5 еще не выпущен, вы можете использовать has_secure_token gem. Также вы можете увидеть мое сообщение в блоге, чтобы узнать больше об этом https://coderwall.com/p/kb97gg/secure-tokens-from-rails-5-to-rails-4-x-and-3-x

Ответ 6

Вы можете попробовать несколько нижеприведенных трюков, чтобы получить уникальный токен, настолько простой, что я использовал в своем проекте -

CREDIT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

def create_credit_key(count = 25)
    credit_key = ""
    key = CREDIT_CHARS.length
    for i in 1..count
      rand = Random.rand((0.0)..(1.0))
      credit_key += CREDIT_CHARS[(key*rand).to_i].to_s
    end 
    return credit_key
  end

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