【问题标题】:Best way to create unique token in Rails?在 Rails 中创建唯一令牌的最佳方法是什么?
【发布时间】:2011-08-26 15:43:25
【问题描述】:

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

我想,为了减少冲突的机会,我也可以在末尾附加另一个字符串,根据时间或类似的东西生成,但我不希望令牌变得太长。

【问题讨论】:

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


    【解决方案1】:

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

    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)
    

    【讨论】:

    • 我更感兴趣的是确保生成的值不会与已经生成和存储的值发生冲突,而不是创建唯一字符串的方法。
    • 生成的值不会与已经生成的值冲突 - base64 是确定性的,所以如果你有唯一的 id,你就会有唯一的令牌。
    • 我选择了random_string = Digest::MD5.hexdigest("#{ActiveSupport::SecureRandom.hex(10)}-#{DateTime.now.to_s}-#{id}")[1..6],其中 ID 是令牌的 ID。
    • 在我看来Base64::encode64(id.to_s) 违背了使用令牌的目的。您很可能正在使用令牌来掩盖 id 并使没有令牌的任何人都无法访问资源。但是,在这种情况下,有人可以运行 Base64::encode64(<insert_id_here>),然后他们会立即拥有您网站上每个资源的所有令牌。
    • 需要改成这个才能工作string = (Digest::MD5.hexdigest "#{SecureRandom.hex(10)}-#{DateTime.now.to_s}")
    【解决方案2】:

    本文展示了一些非常巧妙的方法:

    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"
    

    【讨论】:

    • 看起来第一种方法与我正在做的类似,但我认为 rand 不是数据库不可知论者?
    • 我不确定我是否遵循这个:if self.new_record? and self.access_token.nil? ... 是在检查以确保令牌尚未存储吗?
    • 您将始终需要对现有令牌进行额外检查。我没有意识到这并不明显。只需添加 validates_uniqueness_of :token 并通过迁移向表添加唯一索引。
    • 这里是博文的作者!是的:在这种情况下,我总是添加一个 db 约束或类似的东西来断言唯一性。
    • 对于那些正在寻找帖子的人(不再存在)...web.archive.org/web/20121026000606/http://blog.logeek.fr/2009/7/…
    【解决方案3】:

    这可能会有所帮助:

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

    如果您想删除除第一个参数“+/=”和第二个参数“0aZ”中的任何字符之外的任何特殊字符,此处的长度为 15。

    如果你想删除多余的空格和换行符而不是添加类似的东西:

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

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

    【讨论】:

    • 如果你不想要像“+/=”这样奇怪的字符,你可以使用 SecureRandom.hex(10) 而不是 base64。
    • SecureRandom.urlsafe_base64 也实现了同样的目标。
    • 这总是独一无二的吗?我需要将它保存在唯一的 db 列中。还有什么需要做的吗?
    【解决方案4】:

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

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

    【讨论】:

    • 是的,这还不错。我通常会寻找更短的字符串,以用作 URL 的一部分。
    • 是的,这至少易于阅读和理解。在某些情况下(例如 Beta 版邀请),40 个字符是很好的,到目前为止这对我来说效果很好。
    • @Slick23 您也可以随时获取字符串的一部分:Digest::SHA1.hexdigest([Time.now, rand].join)[0..10]
    • 在将“客户端 ID”发送到 Google Analytics 的测量协议时,我使用它来混淆 IP 地址。它应该是一个 UUID,但对于任何给定的 IP,我只取 hexdigest 的前 32 个字符。
    • 对于 32 位 IP 地址,很容易找到由@thekingoftruth 生成的所有可能的十六进制摘要的查找表,所以不要认为即使是哈希将是不可逆的。
    【解决方案5】:

    -- 更新--

    截至 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
    

    【讨论】:

    • 不要使用begin/while,使用loop/do
    • @kain 在这种情况下应该使用任何原因loop do(“while...do”类型的循环)(循环需要至少运行一次)而不是begin...while( “do...while” 类型的循环)?
    • 因为 random_token 是在循环范围内,所以这个确切的代码不起作用。
    • @Krule 既然你已经把它变成了一个关注点,你不应该也去掉方法中的ModelName 吗?也许用self.class 代替它?否则,它的复用性不是很好,是吗?
    • 该解决方案没有被弃用,Secure Token 它只是在 Rails 5 中实现,但不能在 Rails 4 或 Rails 3 中使用(与此问题有关)
    【解决方案6】:

    试试这个方法:

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

    这对我很有帮助

    【讨论】:

      【解决方案7】:

      创建一个正确的 mysql varchar 32 GUID

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

      【讨论】:

      • 由于我们试图替换单个字符 '-',您可以使用 tr 而不是 gsub。 SecureRandom.uuid.tr('-','').upcase。检查此link 以比较 tr 和 gsub。
      【解决方案8】:
      def generate_token
          self.token = Digest::SHA1.hexdigest("--#{ BCrypt::Engine.generate_salt }--")
      end
      

      【讨论】:

        【解决方案9】:

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

        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
        

        【讨论】:

          【解决方案10】:

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

          【讨论】:

          • 包装精美!谢谢:D
          • 我得到未定义的局部变量“has_secure_token”。任何想法为什么?
          • @AdrianMatteo 我遇到了同样的问题。据我了解,has_secure_token 是 Rails 5 附带的,但我使用的是 4.x。我已按照this article 上的步骤操作,现在它对我有用。
          【解决方案11】:

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

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

          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
          

          【讨论】:

            猜你喜欢
            • 2017-06-17
            • 1970-01-01
            • 1970-01-01
            • 2021-03-09
            • 2016-12-19
            • 2019-05-12
            • 1970-01-01
            • 2017-08-06
            • 1970-01-01
            相关资源
            最近更新 更多