Active Support 訊息驗證器
MessageVerifier
易於產生和驗證已簽章以防止竄改的訊息。
在 Rails 應用程式中,你可以使用 Rails.application.message_verifier
來管理各個使用案例中驗證器的獨特實體。 深入了解。
這適用於下列狀況,例如,記錄我或自動取消訂閱連結,其中,Session 儲存不適合或不可用。
首先,產生已簽章的訊息
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)
類別公開方法
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
Source: 顯示 | 在 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
Source: 顯示 | 在 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