在 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,但是因为这些是根据用户在应用程序中的操作自动批量创建的(他们下订单并购买令牌,本质上) ,让应用程序抛出错误是不可行的。

我想,为了减少碰撞的几率,我也可以在末尾添加另一个字符串,根据时间或者类似的东西生成一些东西,但是我不希望标记太长。

129830 次浏览

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

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,只需 make

Base64::decode64(string)

在本文中演示了一些非常巧妙的方法:

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"

这可能会有所帮助:

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

如果你想删除任何特殊的字符比放在第一个参数’+/=’和任何字符放在第二个参数’0aZ’和15是这里的长度。

如果你想删除额外的空格和新的行字符,那么添加以下内容:

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

希望这对大家有所帮助。

Ryan Bates 在他的 在测试邀请上进行轨道广播中使用了一小段代码,生成了一个40个字符的字母数字字符串。

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

——更新——

2015年1月9日。开始,解决方案现在在 铁路5 ActiveRecord 的安全令牌实现中实现。

——轨道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 建议,我同意,在这个答案中用 loop do...break unless...end代替 begin...end..while,因为以前的实现可能在将来被删除。

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

试试这样:

从 Ruby 1.9开始,uuid 生成是内置的。
用 Ruby 生成 Guids

这对我很有帮助

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

SecureRandom.uuid.gsub('-','').upcase
def generate_token
self.token = Digest::SHA1.hexdigest("--#{ BCrypt::Engine.generate_salt }--")
end

这可能是一个延迟响应,但是为了避免使用循环,您还可以递归地调用该方法。对我来说,它看起来和感觉起来都稍微干净一点。

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

您可以使用 user has _ security _ 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"

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

我正在做类似的事情来为一个模型生成一个唯一的新标记:

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