21

我想在我的控制器中为“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 

还是有更清洁的方法来做到这一点?

4

6 回答 6

54

如果您的令牌足够长并且由加密安全的 [伪] 随机数生成器生成,那么您无需验证令牌是否唯一。您不需要循环中生成令牌。

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 位令牌不唯一的概率非常小,几乎为零。在生成了大约 2 64 = 18,446,744,073,709,551,616 = 1.845 x 10 19 个令牌之后,只有50% 的机会出现重复。如果您开始每秒生成 10 亿个令牌,大约需要 2 64 /(10 9 *3600*24*365.25) = 600,直到有 50% 的机会发生任何重复。

但是您每秒不会生成 10 亿个令牌。让我们慷慨一点,假设您每秒生成一个令牌。直到有 50% 的几率发生一次碰撞的时间框架变成了6000 亿年。在那之前很久,这颗行星就会被太阳吞没。

于 2013-09-02T00:55:20.630 回答
34

我找到的最干净的解决方案:

@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 个用户在完全相同的时刻以完全相同的令牌保存,则会引发异常

于 2013-09-01T23:42:17.067 回答
4

我有许多模型应用了独特的令牌。出于这个原因,我Tokenedapp/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

但是,是的,您的代码看起来也不错。

于 2013-09-01T01:16:26.777 回答
2

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

于 2015-05-01T20:03:52.193 回答
1

也许您可以使用实际时间做一些事情。然后,您无需检查令牌是否已被用户使用。

new_token = Digest::MD5.hexdigest(Time.now.to_i.to_s + rand(999999999).to_s)
user.user_info_token = new_token
于 2013-09-01T01:49:25.610 回答
0

您可以尝试以下一些技巧来获得唯一的令牌,我在我的项目中使用它非常简单 -

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

使用摘要再次更容易,这里我尝试在不使用任何算法的情况下生成。

于 2015-12-22T11:50:09.467 回答