--2022年年底更新--
我回答这个问题已经有一段时间了。事实上,我甚至七年没有看过这个答案。我也看到许多依赖Rails运行其业务的组织使用了这段代码。
说实话,现在我不认为我的早期解决方案或Rails的实现是一个好方法。它使用了回调函数,可能很难调试,并且是悲观的,即使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 安全令牌实现中实施。
-- 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 建议并且我也同意在这个答案中用 loop do...break unless...end
替换 begin...end..while
,因为前一种实现可能会在未来被移除。
编辑2:
对于Rails 4和concerns,我建议将其移至concern中。
class ModelName < ActiveRecord::Base
include Tokenable
end
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
ModelName
吗?也许用self.class
替换它会更好吧?否则,它就不是很可重用了,对吧? - paracycleurlsafe_base64
来演示一个原则。据我所知,没有任何理由不使用uuid
。 - Krule