这是我正在使用的。令牌不一定非要听到才能猜测,它更像是一个短 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
,但由于这些是根据用户在应用程序中的操作自动批量创建的(他们下订单并购买令牌,本质上),让应用程序抛出错误是不可行的。
我想,为了减少冲突的机会,我也可以在末尾附加另一个字符串,根据时间生成的东西或类似的东西,但我不希望令牌太长。
更新 EOY 2022 --
我已经有一段时间没有回答这个问题了。如此之多,以至于我什至~7年没有看过这个答案。我也看到过许多依赖Rails来运营业务的组织使用过这段代码。
TBH,这些天我不认为我以前的解决方案,或者Rails如何实现它,是一个伟大的解决方案。它使用可以进行PITA调试的回调,并且本质上是悲观的🙁,即使SecureRandom.urlsafe_base64
发生冲突的可能性非常低。这适用于长期和短期代币。
我建议的可能更好的方法是对此保持乐观😊。在所选数据库中对令牌设置唯一约束,然后尝试保存它。如果保存产生异常,请重试,直到成功。
class ModelName < ActiveRecord::Base
def persist_with_random_token!(attempts = 10)
retries ||= 0
token = SecureRandom.urlsafe_base64(nil, false)
save!
rescue ActiveRecord::RecordNotUnique => e
raise if (retries += 1) > attempts
Rails.logger.warn("random token, unlikely collision number #{retries}")
token = SecureRandom.urlsafe_base64(16, false)
retry
end
end
结果如何?
- 少一个查询,因为我们没有事先检查令牌的存在。
- 总的来说,因为它更快。
- 不使用回调,这使得调试更容易。
- 如果发生碰撞,则有一个回退机制。
- 发生冲突时的日志跟踪(指标)
- 也许是时候清理旧代币了吗,
- 或者当我们需要去
SecureRandom.urlsafe_base64(32, false)
时,我们是否达到了不太可能的记录数量?
--更新--
截至2015年1月9日。 该解决方案现已在Rails 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
Ryan Bates 在他的 Railscast 中对 beta 邀请使用了一点很好的代码。这将生成一个 40 个字符的字母数字字符串。
Digest::SHA1.hexdigest([Time.now, rand].join)
这可能是一个延迟的响应,但为了避免使用循环,你也可以递归地调用该方法。对我来说,它看起来和感觉都稍微干净一些。
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
本文演示了一些非常巧妙的方法:
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"
如果你想要一些独特的东西,你可以使用这样的东西:
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)
这可能会有所帮助:
SecureRandom.base64(15).tr('+/=', '0aZ')
如果要删除任何特殊字符,请放入第一个参数"+/=",放入第二个参数"0aZ"和15中的任何字符是此处的长度。
如果要删除多余的空格和换行符,请添加以下内容:
SecureRandom.base64(15).tr('+/=', '0aZ').strip.delete("n")
希望这对任何人都有帮助。
试试这个方式:
从 Ruby 1.9 开始,内置了 uuid 生成。使用 SecureRandom.uuid
函数.
在 Ruby 中生成 GUID
这对我很有帮助
你可以用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"
要创建一个正确的 mysql, varchar 32 GUID
SecureRandom.uuid.gsub('-','').upcase
def generate_token
self.token = Digest::SHA1.hexdigest("--#{ BCrypt::Engine.generate_salt }--")
end
Rails 7 具有此功能。请参阅以下示例:
# Schema: User(token:string, auth_token:string)
class User < ActiveRecord::Base
has_secure_token
has_secure_token :auth_token, length: 36
end
user = User.new
user.save
user.token # => "pX27zsMN2ViQKta1bGfLmVJE"
user.auth_token # => "tU9bLuZseefXQ4yQxQo8wjtBvsAfPc78os6R"
user.regenerate_token # => true
user.regenerate_auth_token # => true
我认为令牌应该像密码一样处理。因此,它们应该在数据库中加密。
我正在做这样的事情来为模型生成一个唯一的新令牌:
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