Active Support Message Verifier¶ ↑
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) Link
使用 secret 初始化一个新的 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 类型进行往返处理,并可能提供更好的性能。但是,这需要msgpackgem。在使用 Rails 时,默认取决于
config.active_support.message_serializer。否则,默认为:marshal。 :url_safe-
默认情况下,
MessageVerifier生成符合 RFC 4648 的字符串,这些字符串不适用于 URL。换句话说,它们可能包含“+”和“/”。如果您想生成 URL 安全的字符串(符合 RFC 4648 中的“Base 64 Encoding with URL and Filename Safe Alphabet”),您可以传递true。请注意,MessageVerifier将始终接受 URL 安全和 URL 不安全的编码消息,以便在两种设置之间平滑过渡。 :force_legacy_metadata_serializer-
是否使用旧的元数据序列化器,该序列化器先序列化消息,然后将其包装在也已序列化的信封中。这是 Rails 7.0 及以下版本的默认设置。
如果您不传递真值,则默认值将使用
config.active_support.use_message_serializer_for_metadata设置。
实例公共方法
generate(value, **options) Link
为提供的值生成签名消息。
消息使用 MessageVerifier 的 secret 进行签名。返回 Base64 编码的消息与生成的签名连接。
verifier = ActiveSupport::MessageVerifier.new("secret") verifier.generate("signed message") # => "BAhJIhNzaWduZWQgbWVzc2FnZQY6BkVU--f67d5f27c3ee0b8483cebf2103757455e947493b"
Options¶ ↑
: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。)
valid_message?(message) Link
检查签名消息是否可以由使用 MessageVerifier secret 对对象进行签名生成。
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
verified(message, **options) Link
使用 MessageVerifier 的 secret 解码签名消息。
verifier = ActiveSupport::MessageVerifier.new("secret") signed_message = verifier.generate("signed message") verifier.verified(signed_message) # => "signed message"
如果消息不是用相同的 secret 签名的,则返回 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
Options¶ ↑
: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
# File activesupport/lib/active_support/message_verifier.rb, line 224 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) Link
使用 MessageVerifier 的 secret 解码签名消息。
verifier = ActiveSupport::MessageVerifier.new("secret") signed_message = verifier.generate("signed message") verifier.verify(signed_message) # => "signed message"
如果消息不是用相同的 secret 签名的或不是 Base64 编码的,则引发 InvalidSignature。
other_verifier = ActiveSupport::MessageVerifier.new("different_secret") other_verifier.verify(signed_message) # => ActiveSupport::MessageVerifier::InvalidSignature
Options¶ ↑
: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
# File activesupport/lib/active_support/message_verifier.rb, line 262 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