класс ActiveSupport::MessageVerifier
MessageVerifier упрощает генерацию и проверку подписанных сообщений, предотвращая их подделку.
Это полезно в таких случаях, как токены «запомнить меня» и ссылки на отписку, когда хранилище сессии не подходит или недоступно.
Запомнить меня:
cookies[:remember_me] = @verifier.generate([@user.id, 2.weeks.from_now])
В фильтре аутентификации:
id, time = @verifier.verify(cookies[:remember_me]) if Time.now < time self.current_user = User.find(id) end
По умолчанию для сериализации сообщения используется Marshal. Если вы хотите использовать другой метод сериализации, вы можете установить сериализатор в хеш-опций при инициализации:
@verifier = ActiveSupport::MessageVerifier.new('s3Krit', serializer: YAML)
MessageVerifier по умолчанию создаёт HMAC подписи с использованием алгоритма хеширования SHA1. Если вы хотите использовать другой алгоритм хеширования, вы можете изменить его, предоставив :digest ключ в качестве опции при инициализации верификатора:
@verifier = ActiveSupport::MessageVerifier.new('s3Krit', digest: 'SHA256')
Ограничение сообщений определённым назначением
По умолчанию любое сообщение может использоваться в вашем приложении. Но их также можно ограничить определённым :purpose.
token = @verifier.generate("this is the chair", purpose: :login)
Затем при проверке необходимо передать это же назначение, чтобы получить данные обратно:
@verifier.verified(token, purpose: :login) # => "this is the chair" @verifier.verified(token, purpose: :shipping) # => nil @verifier.verified(token) # => nil @verifier.verify(token, purpose: :login) # => "this is the chair" @verifier.verify(token, purpose: :shipping) # => ActiveSupport::MessageVerifier::InvalidSignature @verifier.verify(token) # => ActiveSupport::MessageVerifier::InvalidSignature
Аналогично, если у сообщения нет назначения, оно не будет возвращено при проверке со специфическим назначением.
token = @verifier.generate("the conversation is lively")
@verifier.verified(token, purpose: :scare_tactics) # => nil
@verifier.verified(token) # => "the conversation is lively"
@verifier.verify(token, purpose: :scare_tactics) # => ActiveSupport::MessageVerifier::InvalidSignature
@verifier.verify(token) # => "the conversation is lively"
Установление срока действия сообщений
По умолчанию сообщения действуют вечно, и проверка через год всё ещё вернёт исходное значение. Но сообщения можно установить с истекающим сроком с помощью :expires_in или :expires_at.
@verifier.generate("parcel", expires_in: 1.month)
@verifier.generate("doowad", expires_at: Time.now.end_of_year)
Тогда сообщения могут быть проверены и возвращены до времени истечения срока действия. После этого, метод 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 110 def initialize(secret, digest: nil, serializer: nil) raise ArgumentError, "Secret should not be nil." unless secret @secret = secret @digest = digest&.to_s || "SHA1" @serializer = serializer || Marshal end
Методы публичного экземпляра
# File activesupport/lib/active_support/message_verifier.rb, line 188
def generate(value, expires_at: nil, expires_in: nil, purpose: nil)
data = encode(Messages::Metadata.wrap(@serializer.dump(value), expires_at: expires_at, expires_in: expires_in, purpose: purpose))
"#{data}#{SEPARATOR}#{generate_digest(data)}"
end Генерирует подписанное сообщение для предоставленного значения.
Сообщение подписывается секретом MessageVerifier. Возвращает закодированное в Base64 сообщение, соединенное с сгенерированной подписью.
verifier = ActiveSupport::MessageVerifier.new 's3Krit' verifier.generate 'a private message' # => "BAhJIhRwcml2YXRlLW1lc3NhZ2UGOgZFVA==--e2d724331ebdee96a10fb99b089508d1c72bd772"
# File activesupport/lib/active_support/message_verifier.rb, line 126 def valid_message?(signed_message) data, digest = get_data_and_digest_from(signed_message) digest_matches_data?(digest, data) end
Проверяет, можно ли было сгенерировать подписанное сообщение путем подписи объекта секретом MessageVerifier.
verifier = ActiveSupport::MessageVerifier.new 's3Krit' signed_message = verifier.generate 'a private 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 152
def verified(signed_message, purpose: nil, **)
data, digest = get_data_and_digest_from(signed_message)
if digest_matches_data?(digest, data)
begin
message = Messages::Metadata.verify(decode(data), purpose)
@serializer.load(message) if message
rescue ArgumentError => argument_error
return if argument_error.message.include?("invalid base64")
raise
end
end
end Декодирует подписанное сообщение, используя секрет MessageVerifier.
verifier = ActiveSupport::MessageVerifier.new 's3Krit' signed_message = verifier.generate 'a private message' verifier.verified(signed_message) # => 'a private message'
Возвращает nil, если сообщение не было подписано с тем же секретом.
other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit' 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
# File activesupport/lib/active_support/message_verifier.rb, line 177 def verify(*args, **options) verified(*args, **options) || raise(InvalidSignature) end
Декодирует подписанное сообщение, используя секрет MessageVerifier.
verifier = ActiveSupport::MessageVerifier.new 's3Krit' signed_message = verifier.generate 'a private message' verifier.verify(signed_message) # => 'a private message'
Вызывает InvalidSignature, если сообщение не было подписано с тем же секретом или не было закодировано в Base64.
other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit' other_verifier.verify(signed_message) # => ActiveSupport::MessageVerifier::InvalidSignature
© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.