Spec-Zone.ru › Ruby on Rails 8.1

класс 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)

Открытые методы класса

new (secret, **options) Показать исходный код
# File activesupport/lib/active_support/message_verifier.rb, line 167
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. Обратите внимание, что MessageVerifier всегда принимает как безопасные, так и небезопасные для URL кодированные сообщения, что позволяет плавно перейти с одного параметра на другой.

: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 306
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 183
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 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

Декодирует подписанное сообщение с помощью секретного ключа 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 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

Декодирует подписанное сообщение с помощью секретного ключа 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