Spec-Zone.ru › Ruby on Rails 4.1

модуль ActionController::HttpAuthentication::Digest

Обеспечивает простую реализацию аутентификации HTTP Digest.

Пример простой аутентификации Digest

require 'digest/md5'
class PostsController < ApplicationController
  REALM = "SuperSecret"
  USERS = {"dhh" => "secret", #plain text password
           "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))}  #ha1 digest password

  before_action :authenticate, except: [:index]

  def index
    render plain: "Everyone can see me!"
  end

  def edit
    render plain: "I'm only accessible if you know the password"
  end

  private
    def authenticate
      authenticate_or_request_with_http_digest(REALM) do |username|
        USERS[username]
      end
    end
end

Примечания

Блок authenticate_or_request_with_http_digest должен возвращать пароль пользователя или хеш ha1, чтобы фреймворк мог правильно его вычислить и проверить данные пользователя. Возвращение nil приведёт к сбою аутентификации.

Хранение хеша ha1: MD5(имя_пользователя:область:пароль), лучше, чем хранение простого пароля. Если файл паролей или база данных будут скомпрометированы, злоумышленник сможет использовать хеш ha1 для аутентификации как пользователь в этом realm, но не сможет получить пароль пользователя для использования на других сайтах.

В редких случаях веб-серверы или прокси-серверы удаляют заголовки авторизации перед получением запроса приложением. Для отладки этой ситуации можно вести логирование всех переменных среды и проверить наличие HTTP_AUTHORIZATION и других заголовков.

Открытые методы экземпляра

authenticate(запрос, область, &процедура_пароля) Показать исходный код

Возвращает false при успешном ответе, true в противном случае

# File actionpack/lib/action_controller/metal/http_authentication.rb, line 196
def authenticate(request, realm, &password_procedure)
  request.authorization && validate_digest_response(request, realm, &password_procedure)
end
authentication_header(контроллер, область) Показать исходный код
# File actionpack/lib/action_controller/metal/http_authentication.rb, line 254
def authentication_header(controller, realm)
  secret_key = secret_token(controller.request)
  nonce = self.nonce(secret_key)
  opaque = opaque(secret_key)
  controller.headers["WWW-Authenticate"] = %Q(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce}", opaque="#{opaque}")
end
authentication_request(контроллер, область, сообщение = nil) Показать исходный код
# File actionpack/lib/action_controller/metal/http_authentication.rb, line 261
def authentication_request(controller, realm, message = nil)
  message ||= "HTTP Digest: Access denied.\n"
  authentication_header(controller, realm)
  controller.status = 401
  controller.response_body = message
end
decode_credentials(заголовок) Показать исходный код
# File actionpack/lib/action_controller/metal/http_authentication.rb, line 247
def decode_credentials(header)
  ActiveSupport::HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/, '').split(',').map do |pair|
    key, value = pair.split('=', 2)
    [key.strip, value.to_s.gsub(/^"|"$/,'').delete('\')]
  end]
end
decode_credentials_header(запрос) Показать исходный код
# File actionpack/lib/action_controller/metal/http_authentication.rb, line 243
def decode_credentials_header(request)
  decode_credentials(request.authorization)
end
encode_credentials(http_метод, данные_аутентификации, пароль, пароль_это_ha1) Показать исходный код
# File actionpack/lib/action_controller/metal/http_authentication.rb, line 238
def encode_credentials(http_method, credentials, password, password_is_ha1)
  credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password, password_is_ha1)
  "Digest " + credentials.sort_by {|x| x[0].to_s }.map {|v| "#{v[0]}='#{v[1]}'" }.join(', ')
end
expected_response(http_метод, uri, данные_аутентификации, пароль, password_is_ha1=true) Показать исходный код

Возвращает ожидаемый ответ для запроса http_method к uri с декодированными credentials и ожидаемым password. Дополнительный параметр password_is_ha1 по умолчанию установлен в true, так как лучшей практикой является хранение хэша ha1, а не простого пароля.

# File actionpack/lib/action_controller/metal/http_authentication.rb, line 228
def expected_response(http_method, uri, credentials, password, password_is_ha1=true)
  ha1 = password_is_ha1 ? password : ha1(credentials, password)
  ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(':'))
  ::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(':'))
end
ha1(данные_аутентификации, пароль) Показать исходный код
# File actionpack/lib/action_controller/metal/http_authentication.rb, line 234
def ha1(credentials, password)
  ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(':'))
end
nonce(секретный_ключ, время = Time.now) Показать исходный код

Использует MD5 хеширование, основанное на времени, для генерации значения, используемого только один раз.

Указывается строка, генерируемая сервером, которая должна быть уникальной каждый раз, когда создаётся ответ 401. Рекомендуется использовать строку в формате base64 или шестнадцатеричном виде. В частности, так как строка передаётся в строках заголовка как строка в кавычках, символ двойной кавычки запрещён.

Содержание nonce зависит от реализации. Качество реализации зависит от правильного выбора. Например, nonce может быть построен как base64 кодирование

time-stamp H(time-stamp ":" ETag ":" private-key)

где временная_метка — это сгенерированное сервером время или другое неповторяющееся значение, ETag — это значение HTTP заголовка ETag, связанного с запрашиваемым объектом, а закрытый_ключ — данные, известные только серверу. С nonce в таком формате сервер пересчитает хеш-значение после получения заголовка аутентификации клиента и отклонит запрос, если он не соответствует nonce из этого заголовка или если значение временной_метки не является достаточно свежим. Таким образом, сервер может ограничить время действия nonce. Включение ETag предотвращает повторный запрос для обновлённой версии ресурса. (Примечание: включение IP-адреса клиента в nonce, кажется, даёт серверу возможность ограничить повторное использование nonce тем же клиентом, который его первоначально получил. Однако это нарушает работу ферм прокси, где запросы от одного пользователя часто проходят через разные прокси в ферме. Кроме того, подмена IP-адреса не так сложна.)

Реализация может выбрать отказ от ранее использованного nonce или хеша, чтобы защитить от атак с повтором. Или реализация может выбрать использование одноразовых nonce или хешей для POST, PUT или PATCH запросов и временную_метку для GET запросов. Более подробную информацию о связанных проблемах см. в разделе 4 этого документа.

Nonce недоступен клиенту. Состоит из Time и хэша Time с секретным ключом из секрета сессии Rails, сгенерированного при создании проекта. Гарантирует, что время нельзя изменить клиентом.

# File actionpack/lib/action_controller/metal/http_authentication.rb, line 306
def nonce(secret_key, time = Time.now)
  t = time.to_i
  hashed = [t, secret_key]
  digest = ::Digest::MD5.hexdigest(hashed.join(":"))
  ::Base64.strict_encode64("#{t}:#{digest}")
end
opaque(секретный_ключ) Показать исходный код

Непрозрачный, основанный на случайной генерации — но меняющийся с каждым запросом?

# File actionpack/lib/action_controller/metal/http_authentication.rb, line 325
def opaque(secret_key)
  ::Digest::MD5.hexdigest(secret_key)
end
secret_token(запрос) Показать исходный код
# File actionpack/lib/action_controller/metal/http_authentication.rb, line 268
def secret_token(request)
  key_generator  = request.env["action_dispatch.key_generator"]
  http_auth_salt = request.env["action_dispatch.http_auth_salt"]
  key_generator.generate_key(http_auth_salt)
end
validate_digest_response(запрос, область, &процедура_пароля) Показать исходный код

Возвращает false, если значение ответа аутентификации запроса не совпадает с ожидаемым значением. Сначала попробуйте использовать пароль как хэш ha1. Если это не сработает, попробуйте использовать его как обычный текст.

# File actionpack/lib/action_controller/metal/http_authentication.rb, line 203
def validate_digest_response(request, realm, &password_procedure)
  secret_key  = secret_token(request)
  credentials = decode_credentials_header(request)
  valid_nonce = validate_nonce(secret_key, request, credentials[:nonce])

  if valid_nonce && realm == credentials[:realm] && opaque(secret_key) == credentials[:opaque]
    password = password_procedure.call(credentials[:username])
    return false unless password

    method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD']
    uri    = credentials[:uri]

    [true, false].any? do |trailing_question_mark|
      [true, false].any? do |password_is_ha1|
        _uri = trailing_question_mark ? uri + "?" : uri
        expected = expected_response(method, _uri, credentials, password, password_is_ha1)
        expected == credentials[:response]
      end
    end
  end
end
validate_nonce(секретный_ключ, запрос, значение, seconds_to_timeout=5*60) Показать исходный код

Возможно, потребуется более короткое время ожидания в зависимости от того, является ли запрос PATCH, PUT или POST, и является ли клиент браузером или веб-сервисом. Может быть значительно короче, если реализован директива Stale. Это позволит пользователю использовать новый nonce без повторного запроса имени пользователя и пароля.

# File actionpack/lib/action_controller/metal/http_authentication.rb, line 318
def validate_nonce(secret_key, request, value, seconds_to_timeout=5*60)
  return false if value.nil?
  t = ::Base64.decode64(value).split(":").first.to_i
  nonce(secret_key, t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout
end

© 2004–2016 David Heinemeier Hansson
Licensed under the MIT License.

Spec-Zone.ru

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