在Rails中创建唯一令牌的最佳方法?

时间:2011-05-16 17:57:22

标签: ruby-on-rails ruby-on-rails-3 random guid

这是我正在使用的。不一定要听到令牌的猜测,它更像是一个简短的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,但因为这些是根据用户在应用程序中的操作自动批量创建的(他们下订单和从本质上来说,购买令牌,让应用程序抛出错误是不可行的。

我想,我也可以减少碰撞的几率,在最后附加另一个字符串,根据时间或类似的东西生成的东西,但我不希望令牌太长。

11 个答案:

答案 0 :(得分:325)

- 更新 -

2015年1月9日开始。此解决方案现已在 Rails 5 ActiveRecord's secure token implementation中实施。

- 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,因为以后的实施可能会在将来被删除。

编辑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

答案 1 :(得分:50)

Ryan Bates在Railscast on beta invitations中使用了一小段代码。这将生成一个40个字符的字母数字字符串。

Digest::SHA1.hexdigest([Time.now, rand].join)

答案 2 :(得分:30)

在本文中演示了一些非常流畅的方法:

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"

答案 3 :(得分:28)

这可能是迟到的响应,但为了避免使用循环,您还可以递归调用该方法。它的外观和感觉对我来说都比较干净。

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

答案 4 :(得分:17)

如果你想要一些独特的东西,你可以使用这样的东西:

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这样的id,生成的令牌就像“MTAwMDA =”(并且您可以轻松地将其解码为id,只需制作

Base64::decode64(string)

答案 5 :(得分:14)

这可能会有所帮助:

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

如果你想删除任何特殊字符,而不是放在第一个参数'+ / ='中,并且任何字符放在第二个参数'0aZ'中,15是这里的长度。

如果你想删除多余的空格和换行符而不是添加以下内容:

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

希望这对任何人都有帮助。

答案 6 :(得分:7)

尝试这种方式:

从Ruby 1.9开始,uuid生成就是内置的。使用SecureRandom.uuid功能。
Generating Guids in Ruby

这对我有帮助

答案 7 :(得分:6)

您可以使用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"

答案 8 :(得分:5)

创建一个合适的mysql,varchar 32 GUID

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

答案 9 :(得分:1)

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

答案 10 :(得分:0)

我认为令牌应像密码一样处理。因此,它们应该在数据库中加密。

我正在做这样的事情来为模型生成一个唯一的新令牌:

CIRAWFilterOption(s)