Active Support 消息验证器
MessageVerifier
使得生成和验证经过签名的消息变得容易,以防止篡改。
在 Rails 应用程序中,可以使用 Rails.application.message_verifier
为每个用例管理验证器的唯一实例。 了解更多.
这在记住我的令牌和自动退订链接等情况下很有用,在这些情况下,会话存储不适合或不可用。
首先,生成一条签名消息
cookies[:remember_me] = Rails.application.message_verifier(:remember_me).generate([@user.id, 2.weeks.from_now])
稍后验证该消息
id, time = Rails.application.message_verifier(:remember_me).verify(cookies[:remember_me])
if time.future?
self.current_user = User.find(id)
end
签名不是加密
签名消息不会被加密。有效负载只是被编码(默认情况下为 Base64)并且可以被任何人解码。签名只是保证消息没有被篡改。例如
message = Rails.application.message_verifier('my_purpose').generate('never put secrets here')
# => "BAhJIhtuZXZlciBwdXQgc2VjcmV0cyBoZXJlBjoGRVQ=--a0c1c0827919da5e949e989c971249355735e140"
Base64.decode64(message.split("--").first) # no key needed
# => 'never put secrets here'
如果您还需要加密内容,则必须使用 ActiveSupport::MessageEncryptor
。
将消息限制为特定目的
不建议在应用程序中将同一个验证器用于不同的目的。这样做可能会允许恶意攻击者重新使用签名消息来执行未经授权的操作。您可以通过将签名消息限制为特定的 :purpose
来降低这种风险。
token = @verifier.generate("signed message", purpose: :login)
然后,验证时必须传递相同的目的才能将数据取回
@verifier.verified(token, purpose: :login) # => "signed message"
@verifier.verified(token, purpose: :shipping) # => nil
@verifier.verified(token) # => nil
@verifier.verify(token, purpose: :login) # => "signed message"
@verifier.verify(token, purpose: :shipping) # => raises ActiveSupport::MessageVerifier::InvalidSignature
@verifier.verify(token) # => raises ActiveSupport::MessageVerifier::InvalidSignature
同样,如果消息没有目的,则在使用特定目的进行验证时不会返回。
token = @verifier.generate("signed message")
@verifier.verified(token, purpose: :redirect) # => nil
@verifier.verified(token) # => "signed message"
@verifier.verify(token, purpose: :redirect) # => raises ActiveSupport::MessageVerifier::InvalidSignature
@verifier.verify(token) # => "signed message"
过期消息
默认情况下,消息会永远存在,并且一年后验证仍然会返回原始值。但消息可以设置为在给定时间使用 :expires_in
或 :expires_at
过期。
@verifier.generate("signed message", expires_in: 1.month)
@verifier.generate("signed message", expires_at: Time.now.end_of_year)
Messages
然后可以被验证并返回,直到过期。之后,verified
方法返回 nil
,而 verify
抛出 ActiveSupport::MessageVerifier::InvalidSignature
。
轮换密钥
MessageVerifier
还支持通过回退到验证器堆栈来轮换旧配置。调用 rotate
来构建和添加验证器,以便 verified
或 verify
也将尝试使用回退进行验证。
默认情况下,任何轮换的验证器都会使用主验证器的值,除非另有指定。
您将为验证器提供新的默认值
verifier = ActiveSupport::MessageVerifier.new(@secret, digest: "SHA512", serializer: JSON)
然后,通过将它们添加为回退,逐步轮换旧值。使用旧值生成的任何消息都会一直有效,直到轮换被删除。
verifier.rotate(old_secret) # Fallback to an old secret instead of @secret.
verifier.rotate(digest: "SHA256") # Fallback to an old digest instead of SHA512.
verifier.rotate(serializer: Marshal) # Fallback to an old serializer instead of JSON.
尽管以上内容很可能被合并为一个轮换
verifier.rotate(old_secret, digest: "SHA256", serializer: Marshal)
- G
- N
- V
类公有方法
new(secret, **options) 链接
使用签名密钥初始化一个新的 MessageVerifier
。
选项
:digest
-
Digest
用于签名。默认值为"SHA1"
。有关备选方案,请参见OpenSSL::Digest
。 :serializer
-
用于序列化消息数据的序列化器。您可以指定任何响应
dump
和load
的对象,或者您可以从几个预配置的序列化器中进行选择::marshal
、:json_allow_marshal
、:json
、:message_pack_allow_marshal
、:message_pack
。预配置的序列化器包含一个回退机制来支持多种反序列化格式。例如,
:marshal
序列化器将使用Marshal
进行序列化,但可以使用Marshal
、ActiveSupport::JSON
或ActiveSupport::MessagePack
进行反序列化。这使得在序列化器之间迁移变得容易。:marshal
、:json_allow_marshal
和:message_pack_allow_marshal
序列化器支持使用Marshal
进行反序列化,但其他序列化器不支持。请注意,Marshal
在消息签名密钥泄露的情况下,可能是反序列化攻击的潜在媒介。如果可能,请选择不支持Marshal
的序列化器。:message_pack
和:message_pack_allow_marshal
序列化器使用ActiveSupport::MessagePack
,它可以往返于一些JSON
不支持的 Ruby 类型,并且可能会提供更好的性能。但是,这些需要msgpack
gem。在使用 Rails 时,默认值取决于
config.active_support.message_serializer
。否则,默认值为:marshal
。 :url_safe
-
默认情况下,
MessageVerifier
生成符合 RFC 4648 的字符串,这些字符串不是 URL 安全的。换句话说,它们可能包含“+”和“/”。如果您希望生成 URL 安全的字符串(符合 RFC 4648 中的“用于 URL 和文件名安全的字母表的 Base 64 编码”),您可以传递true
。 :force_legacy_metadata_serializer
-
是否使用旧的元数据序列化器,它首先序列化消息,然后将其包装在一个也经过序列化的信封中。这是 Rails 7.0 及以下版本中的默认值。
如果您没有传递一个真值,则默认值使用
config.active_support.use_message_serializer_for_metadata
设置。
来源:显示 | 在 GitHub 上
# File activesupport/lib/active_support/message_verifier.rb, line 165 def initialize(secret, **options) raise ArgumentError, "Secret should not be nil." unless secret super(**options) @secret = secret @digest = options[:digest]&.to_s || "SHA1" end
实例公有方法
generate(value, **options) 链接
为提供的 value 生成一条签名消息。
消息使用 MessageVerifier
的密钥进行签名。返回与生成的签名连接的 Base64 编码消息。
verifier = ActiveSupport::MessageVerifier.new("secret")
verifier.generate("signed message") # => "BAhJIhNzaWduZWQgbWVzc2FnZQY6BkVU--f67d5f27c3ee0b8483cebf2103757455e947493b"
选项
:expires_at
-
消息过期的日期时间。在此日期时间之后,消息验证将失败。
message = verifier.generate("hello", expires_at: Time.now.tomorrow) verifier.verified(message) # => "hello" # 24 hours later... verifier.verified(message) # => nil verifier.verify(message) # => raises ActiveSupport::MessageVerifier::InvalidSignature
:expires_in
-
消息有效的持续时间。在此持续时间过后,消息验证将失败。
message = verifier.generate("hello", expires_in: 24.hours) verifier.verified(message) # => "hello" # 24 hours later... verifier.verified(message) # => nil verifier.verify(message) # => raises ActiveSupport::MessageVerifier::InvalidSignature
:purpose
-
消息的目的。如果指定了目的,则验证消息时必须指定相同目的;否则,验证将失败。(参见
verified
和verify
。)
来源:显示 | 在 GitHub 上
# File activesupport/lib/active_support/message_verifier.rb, line 304 def generate(value, **options) create_message(value, **options) end
valid_message?(message) 链接
检查使用 MessageVerifier
的密钥对对象进行签名是否可以生成一条签名消息。
verifier = ActiveSupport::MessageVerifier.new("secret")
signed_message = verifier.generate("signed message")
verifier.valid_message?(signed_message) # => true
tampered_message = signed_message.chop # editing the message invalidates the signature
verifier.valid_message?(tampered_message) # => false
来源:显示 | 在 GitHub 上
# File activesupport/lib/active_support/message_verifier.rb, line 181 def valid_message?(message) !!catch_and_ignore(:invalid_message_format) { extract_encoded(message) } end
verified(message, **options) 链接
使用 MessageVerifier
的密钥解码签名消息。
verifier = ActiveSupport::MessageVerifier.new("secret")
signed_message = verifier.generate("signed message")
verifier.verified(signed_message) # => "signed message"
如果消息不是使用相同的密钥签名的,则返回 nil
。
other_verifier = ActiveSupport::MessageVerifier.new("different_secret")
other_verifier.verified(signed_message) # => nil
如果消息不是 Base64 编码的,则返回 nil
。
invalid_message = "f--46a0120593880c733a53b6dad75b42ddc1c8996d"
verifier.verified(invalid_message) # => nil
在解码签名消息时引发任何错误。
incompatible_message = "test--dad7b06c94abba8d46a15fafaef56c327665d5ff"
verifier.verified(incompatible_message) # => TypeError: incompatible marshal file format
选项
:purpose
-
生成消息的目的。如果目的不匹配,
verified
将返回nil
。message = verifier.generate("hello", purpose: "greeting") verifier.verified(message, purpose: "greeting") # => "hello" verifier.verified(message, purpose: "chatting") # => nil verifier.verified(message) # => nil message = verifier.generate("bye") verifier.verified(message) # => "bye" verifier.verified(message, purpose: "greeting") # => nil
来源:显示 | 在 GitHub 上
# File activesupport/lib/active_support/message_verifier.rb, line 222 def verified(message, **options) catch_and_ignore :invalid_message_format do catch_and_raise :invalid_message_serialization do catch_and_ignore :invalid_message_content do read_message(message, **options) end end end end
verify(message, **options) 链接
使用 MessageVerifier
的密钥解码签名消息。
verifier = ActiveSupport::MessageVerifier.new("secret")
signed_message = verifier.generate("signed message")
verifier.verify(signed_message) # => "signed message"
如果消息不是使用相同的密钥签名的或不是 Base64 编码的,则引发 InvalidSignature
。
other_verifier = ActiveSupport::MessageVerifier.new("different_secret")
other_verifier.verify(signed_message) # => ActiveSupport::MessageVerifier::InvalidSignature
选项
:purpose
-
生成消息的目的。如果目的不匹配,
verify
将引发ActiveSupport::MessageVerifier::InvalidSignature
.message = verifier.generate("hello", purpose: "greeting") verifier.verify(message, purpose: "greeting") # => "hello" verifier.verify(message, purpose: "chatting") # => raises InvalidSignature verifier.verify(message) # => raises InvalidSignature message = verifier.generate("bye") verifier.verify(message) # => "bye" verifier.verify(message, purpose: "greeting") # => raises InvalidSignature
来源:显示 | 在 GitHub 上
# File activesupport/lib/active_support/message_verifier.rb, line 260 def verify(message, **options) catch_and_raise :invalid_message_format, as: InvalidSignature do catch_and_raise :invalid_message_serialization do catch_and_raise :invalid_message_content, as: InvalidSignature do read_message(message, **options) end end end end