класс ActiveSupport::MessageVerifier
Верификатор сообщений 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)
Методы публичного класса
# 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
Инициализирует новый 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, и может обеспечить улучшенную производительность. Однако они требуют использования драгоценного камняmsgpack.При использовании 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 304 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 181
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 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 Декодирует подписанное сообщение с использованием секрета 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 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 Декодирует подписанное сообщение с использованием секрета 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.