Spec-Zone.ru › Ruby on Rails 7.2

класс ActiveSupport::MessageVerifier

Родитель:
Messages::Codec

Верификатор сообщений 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)

Методы публичного класса

new(secret, **options) Показать исходный код
# 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.

Вызывает метод родительского класса

Методы публичного экземпляра

generate(value, **options) Показать исходный код
# 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.)

valid_message?(message) Показать исходный код
# 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
verified(message, **options) Показать исходный код
# 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
verify(message, **options) Показать исходный код
# 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.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API