class ActiveSupport::MessageVerifier
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
Ограничение сообщений конкретной целью
Не рекомендуется использовать один и тот же верификатор для разных целей в вашем приложении. Это может позволить злоумышленнику повторно использовать подписанное сообщение для выполнения несанкционированного действия. Вы можете снизить этот риск, ограничив подписанные сообщения конкретной :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)
Методы публичного класса
# File activesupport/lib/active_support/message_verifier.rb, line 153 def initialize(secret, **options) raise ArgumentError, "Secret should not be nil." unless secret super(**options) @secret = secret @digest = options[:digest]&.to_s || "SHA1" end
Инициализирует новый объект 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, который может выполнить обратную сериализацию некоторых типов Ruby, которые не поддерживаютсяJSON, и может обеспечить улучшенную производительность. Однако для них требуется gemmsgpack.При использовании Rails, значение по умолчанию зависит от
config.active_support.message_serializer. В противном случае значение по умолчанию –:marshal. -
:url_safe -
По умолчанию
MessageVerifierгенерирует строки, совместимые с RFC 4648, которые не являются URL-безопасными. Другими словами, они могут содержать “+” и “/”. Если вы хотите сгенерировать URL-безопасные строки (в соответствии с “Кодирование Base 64 с безопасностью URL и имен файлов” в RFC 4648), вы можете передатьtrue. -
:force_legacy_metadata_serializer -
Использовать ли устаревший сериализатор метаданных, который сначала сериализует сообщение, затем помещает его в конверт, который также сериализуется. Это было значением по умолчанию в Rails 7.0 и ниже.
Если вы не передадите истинное значение, значение по умолчанию устанавливается с использованием
config.active_support.use_message_serializer_for_metadata.
Открытые методы экземпляра
# File activesupport/lib/active_support/message_verifier.rb, line 292 def generate(value, **options) create_message(value, **options) end
Генерирует подписанное сообщение для предоставленного значения.
Сообщение подписывается с помощью секретного ключа 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.)
# File activesupport/lib/active_support/message_verifier.rb, line 169
def valid_message?(message)
!!catch_and_ignore(:invalid_message_format) { extract_encoded(message) }
end Проверяет, можно ли было сгенерировать подписанное сообщение путем подписи объекта с помощью секретного ключа 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
# File activesupport/lib/active_support/message_verifier.rb, line 210
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 Декодирует подписанное сообщение с использованием секретного ключа 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
Возвращает nil, если сообщение не закодировано в Base64.
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
# File activesupport/lib/active_support/message_verifier.rb, line 248
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 Декодирует подписанное сообщение с использованием секретного ключа MessageVerifier.
verifier = ActiveSupport::MessageVerifier.new("secret")
signed_message = verifier.generate("signed message")
verifier.verify(signed_message) # => "signed message"
Возникает InvalidSignature, если сообщение не было подписано с тем же секретом или не было закодировано в Base64.
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
© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.