这是我正在使用的。令牌不一定要听才能猜测,它更像是一个短 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
,但因为这些是根据用户在应用程序中的操作自动批量创建的(他们下订单并购买代币,本质上),让应用程序抛出错误是不可行的。
我想,为了减少冲突的机会,在末尾附加另一个字符串,根据时间生成的东西或类似的东西,但我不希望令牌变得太长。
-- 更新 2022 年 EOY --
我回答这个问题已经有一段时间了。以至于我已经有七年没有看过这个答案了。我还看到许多依赖 Rails 来运行业务的组织使用了这段代码。
说实话,这些天我不会认为我之前的解决方案,或者 Rails 如何实现它,是一个很棒的解决方案。它使用可以通过 PITA 进行调试的回调,并且本质上是悲观的 🙁,尽管
SecureRandom.urlsafe_base64
发生冲突的可能性非常低。这对于长期和短期代币都适用。
我建议的一个可能更好的方法是对此保持乐观😊。在所选数据库中对令牌设置唯一约束,然后尝试保存它。如果保存出现异常,请重试直至成功。
class ModelName < ActiveRecord::Base
def persist_with_random_token!(attempts = 10)
retries ||= 0
self.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}")
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 建议,并且我同意,在这个答案中将 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
Railscast on 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
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)
SecureRandom.base64(15).tr('+/=', '0aZ')
如果您想删除任何特殊字符,请在第一个参数“+/=”中添加任何特殊字符,在第二个参数“0aZ”中添加任何字符,15 是此处的长度。
如果您想删除多余的空格和换行符,请添加以下内容:
SecureRandom.base64(15).tr('+/=', '0aZ').strip.delete("\n")
希望这对任何人都有帮助。
SecureRandom.uuid.gsub('-','').upcase
# 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
def generate_token
self.token = Digest::SHA1.hexdigest("--#{ BCrypt::Engine.generate_salt }--")
end
我正在做这样的事情来为模型生成一个独特的新令牌:
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