Лучший способ создать уникальный токен в Rails? - PullRequest
150 голосов
/ 16 мая 2011

Вот что я использую.Маркер не обязательно должен быть услышан, чтобы угадать, он больше похож на короткий 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 намодели, но поскольку они создаются партиями автоматически на основе действий пользователя в приложении (они по сути размещают заказ и покупают токены), приложение не может выдать ошибку.

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

Ответы [ 11 ]

325 голосов
/ 24 августа 2012

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

По состоянию на 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

Редактировать:

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

Edit 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
50 голосов
/ 03 января 2012

Райан Бейтс использует небольшой кусочек кода в своем Railscast при бета-приглашениях .В результате получается буквенно-цифровая строка из 40 символов.

Digest::SHA1.hexdigest([Time.now, rand].join)
30 голосов
/ 16 мая 2011

В этой статье продемонстрировано несколько приятных способов сделать это:

https://web.archive.org/web/20121026000606/http://blog.logeek.fr/2009/7/2/creating-small-unique-tokens-in-ruby

Мой любимый в списке это:

rand(36**8).to_s(36)
=> "uur0cj2h"
29 голосов
/ 03 апреля 2014

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

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
17 голосов
/ 16 мая 2011

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

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

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

Base64::decode64(string)
14 голосов
/ 12 октября 2011

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

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

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

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

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

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

7 голосов
/ 16 апреля 2013

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

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

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

6 голосов
/ 03 декабря 2014

вы можете пользователя has_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"
5 голосов
/ 04 сентября 2013

Для создания правильного, mysql, varchar 32 GUID

SecureRandom.uuid.gsub('-','').upcase
1 голос
/ 26 февраля 2014
def generate_token
    self.token = Digest::SHA1.hexdigest("--#{ BCrypt::Engine.generate_salt }--")
end
...