Лучший способ создать уникальный токен в Rails?

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

def self.create_token
    random_number = SecureRandom.hex(3)
    "1X#{random_number}"

    while Tracker.find_by_token("1X#{random_number}") != nil
      random_number = SecureRandom.hex(3)
      "1X#{random_number}"
    end
    "1X#{random_number}"
  end

Мой столбец базы данных для токена - это уникальный индекс, и я также использую validates_uniqueness_of :token для модели, но потому, что они создаются пакетами автоматически на основе действий пользователя в приложении (они размещают заказ и покупают по существу), не представляется возможным, чтобы приложение выбрасывало ошибку.

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

Ответ 1

- Обновление -

По состоянию на 9 января 2015 года решение теперь реализовано в Rails 5 Реализация активного маркера ActiveRecord.

- Rails 4 и 3 -

Просто для справки в будущем, создавая безопасный случайный токен и гарантируя его уникальность для модели (при использовании Ruby 1.9 и ActiveRecord):

class ModelName < ActiveRecord::Base

  before_create :generate_token

  protected

  def generate_token
    self.token = loop do
      random_token = SecureRandom.urlsafe_base64(nil, false)
      break random_token unless ModelName.exists?(token: random_token)
    end
  end

end

Edit:

@kain предложил, и я согласился заменить begin...end..while на loop do...break unless...end в этом ответе, потому что предыдущая реализация может быть удалена в будущем.

Изменить 2:

С Rails 4 и проблемами я бы рекомендовал переместить это для беспокойства.

# app/models/model_name.rb
class ModelName < ActiveRecord::Base
  include Tokenable
end

# app/models/concerns/tokenable.rb
module Tokenable
  extend ActiveSupport::Concern

  included do
    before_create :generate_token
  end

  protected

  def generate_token
    self.token = loop do
      random_token = SecureRandom.urlsafe_base64(nil, false)
      break random_token unless self.class.exists?(token: random_token)
    end
  end
end

Ответ 4

Это может быть поздний ответ, но чтобы избежать использования цикла, вы также можете вызвать метод рекурсивно. Он выглядит и чувствует себя немного чище для меня.

class ModelName < ActiveRecord::Base

  before_create :generate_token

  protected

  def generate_token
    self.token = SecureRandom.urlsafe_base64
    generate_token if ModelName.exists?(token: self.token)
  end

end

Ответ 5

Если вы хотите что-то уникальное, вы можете использовать что-то вроде этого:

string = (Digest::MD5.hexdigest "#{ActiveSupport::SecureRandom.hex(10)}-#{DateTime.now.to_s}")

однако это приведет к генерации строки из 32 символов.

Существует другой способ:

require 'base64'

def after_create
update_attributes!(:token => Base64::encode64(id.to_s))
end

например, для id как 10000, сгенерированный токен будет похож на "MTAwMDA =" (и вы можете легко декодировать его для id, просто сделайте

Base64::decode64(string)

Ответ 6

Это может быть полезно:

SecureRandom.base64(15).tr('+/=', '0aZ')

Если вы хотите удалить какой-либо специальный символ, чем положить в первый аргумент '+/=', а любой символ, помещенный во второй аргумент '0aZ', и 15 - это длина здесь.

И если вы хотите удалить лишние пробелы и новый символ строки, добавьте такие вещи, как:

SecureRandom.base64(15).tr('+/=', '0aZ').strip.delete("\n")

Надеюсь, это поможет кому угодно.

Ответ 7

Попробуйте следующим образом:

Начиная с Ruby 1.9, построена uuid. Используйте функцию SecureRandom.uuid.
Создание гидов в Ruby

Это было полезно для меня

Ответ 8

вы можете использовать user_secure_token https://github.com/robertomiranda/has_secure_token

действительно прост в использовании

class User
  has_secure_token :token1, :token2
end

user = User.create
user.token1 => "44539a6a59835a4ee9d7b112b48cd76e"
user.token2 => "226dd46af6be78953bde1641622497a8"

Ответ 9

Чтобы создать правильный, mysql, varchar 32 GUID

SecureRandom.uuid.gsub('-','').upcase

Ответ 10

def generate_token
    self.token = Digest::SHA1.hexdigest("--#{ BCrypt::Engine.generate_salt }--")
end

Ответ 11

Я думаю, что токен должен обрабатываться так же, как пароль. Как таковые, они должны быть зашифрованы в БД.

Я делаю что-то вроде этого, чтобы сгенерировать уникальный новый токен для модели:

key = ActiveSupport::KeyGenerator
                .new(Devise.secret_key)
                .generate_key("put some random or the name of the key")

loop do
  raw = SecureRandom.urlsafe_base64(nil, false)
  enc = OpenSSL::HMAC.hexdigest('SHA256', key, raw)

  break [raw, enc] unless Model.exist?(token: enc)
end