Создать журнал после ошибки

Мне нужно написать журнал, когда кто-то не сможет войти в мое приложение (чтобы отслеживать попытки грубой силы). Также я решил записать успешные аутентификации. Поэтому я создал SessionController < Devise:: SessionController и попытался переопределить сеансы # создать такой метод: https://gist.github.com/3884693

Первая часть работает отлично, но когда auth failes rails выбрасывает какое-то исключение и никогда не достигает инструкции if. Поэтому я не знаю, что делать.

Ответ 1

Этот ответ на предыдущий вопрос SO - Devise: Регистрация попыток входа в систему имеет ответ.

Действие create в контроллере разработки вызывает warden.authenticate!, который пытается аутентифицировать пользователя с предоставленными параметрами. Если аутентификация завершилась неудачей, выполните аутентификацию! вызовет приложение для разработки, которое затем запускает новое действие SessionController #. Обратите внимание, что любые фильтры, которые у вас есть для действия create, не будут выполняться, если аутентификация завершается неудачно.

Таким образом, решение заключается в добавлении фильтра после нового действия, которое проверяет содержимое env [ "warden.options" ] и принимает соответствующие меры.

Я опробовал это предложение и смог зарегистрировать как успешные, так и неудачные попытки входа в систему. Вот соответствующий код контроллера:

class SessionsController < Devise::SessionsController
  after_filter :log_failed_login, :only => :new

  def create
    super
    ::Rails.logger.info "\n***\nSuccessful login with email_id : #{request.filtered_parameters["user"]}\n***\n"
  end

  private
  def log_failed_login
    ::Rails.logger.info "\n***\nFailed login with email_id : #{request.filtered_parameters["user"]}\n***\n" if failed_login?
  end 

  def failed_login?
    (options = env["warden.options"]) && options[:action] == "unauthenticated"
  end 
end

В журнал вносятся следующие данные:

Для успешного входа в систему

Started POST "/users/sign_in"
...
...
***
Successful login with email_id : {"email"=>...
***
...
...
Completed 302 Found

Для неудачного входа

Started POST "/users/sign_in"
...
...
Completed 401 Unauthorized 
Processing by SessionsController#new as HTML
...
...
***
Failed login with email_id : {"email"=>...
***
...
...
Completed 302 Found

Ответ 2

У меня был тот же вопрос, но я не смог его решить, используя "warden.options", поскольку в моем случае они были очищены перед перенаправлением на действие sessions#new. Изучив несколько альтернатив, которые, как я считал, были слишком хрупкими (потому что они включали расширение некоторых классов Devise и использование существующих методов), я пришел с помощью обратных вызовов, предоставленных Warden. Он работает лучше для меня, потому что обратный вызов вызывается внутри текущего цикла запроса-ответа, и все параметры сохраняются в объекте env.

Эти обратные вызовы называются и, как представляется, предназначены для решения этих и связанных с этим проблем. И они задокументированы!

Warden поддерживает следующие обратные вызовы с warden-1.2.3:

  • after_set_user
  • after_authentication (полезно для регистрации успешных подписей)
  • after_fetch (псевдоним для after_set_user)
  • before_failure (полезно для входа в систему с ошибкой без знака - пример ниже)
  • after_failed_fetch
  • before_logout
  • on_request

Каждый обратный вызов устанавливается непосредственно в классе Warden::Manager. Чтобы отслеживать неудачную попытку аутентификации, я добавил следующее:

Warden::Manager.before_failure do |env, opts|
  email = env["action_dispatch.request.request_parameters"][:user] &&
          env["action_dispatch.request.request_parameters"][:user][:email]
  # unfortunately, the User object has been lost by the time 
  # we get here; so we take a db hit because I care to see 
  # if the email matched a user account in our system
  user_exists = User.where(email: email).exists?

  if opts[:message] == :unconfirmed
    # this is a special case for me because I'm using :confirmable
    # the login was correct, but the user hasn't confirmed their 
    # email address yet
    ::Rails.logger.info "*** Login Failure: unconfirmed account access: #{email}"
  elsif opts[:action] == "unauthenticated"
    # "unauthenticated" indicates a login failure
    if !user_exists
      # bad email:
      # no user found by this email address
      ::Rails.logger.info "*** Login Failure: bad email address given: #{email}"
    else
      # the user exists in the db, must have been a bad password
      ::Rails.logger.info "*** Login Failure: email-password mismatch: #{email}"
    end
  end
end

Я ожидаю, что вы можете использовать обратный вызов before_logout для отслеживания действий выхода, но я его не тестировал. По-видимому, существуют варианты prepend_.

Ответ 3

Ответ Пракаша полезен, но он не идеален, чтобы полагаться на SessionsController#new для запуска в качестве побочного эффекта. Я считаю, что это чище

class LogAuthenticationFailure < Devise::FailureApp
  def respond
    if request.env.dig('warden.options', :action) == 'unauthenticated'
      Rails.logger.info('...')
    end
    super
  end
end

...

Devise.setup do |config|

config.warden do |manager|
  manager.failure_app = LogAuthenticationFailure
end

Ознакомьтесь с ответом Graeme, если вы предпочитаете использовать обратные вызовы Warden (Devise реализован с использованием Warden).

Ответ 4

Для регистрации журнала вы должны поймать событие destroy, поэтому добавьте следующее в контроллер сеанса (из приведенного выше ответа):

before_filter :log_logout, :only => :destroy  #add this at the top with the other filters

def log_logout
     ::Rails.logger.info "*** Logging out : #{current_user.email} ***\n"  
end

Ответ 5

Я нашел другой способ сделать это, если вы хотите, например, отображать пользовательское сообщение, если вход в систему не удается.

В моей работе, если вход в систему не удается, мы проверяем статус активности (пользовательская логика) и отображаем сообщение, независимо от того, был ли вход правильный или нет.

Немного отладив и прочитав документы хранителя, я теперь знаю: смотритель выполняет throw(:warden, opts), поэтому, согласно документам ruby, throw должен быть захвачен внутри блока catch.

def create
  flash.clear
  login_result = catch(:warden) { super }
  return unless login_failed?(login_result)

  email = params[:user][:email]
  flash[:alert] = # here I call my service that calculates the message
  redirect_to new_user_session_path
end

def login_failed?(login_result)
  login_result.is_a?(Hash) && login_result.key?(:scope) && login_result.key?(:recall)
end

бросить документы: https://ruby-doc.org/core-2.6.3/Kernel.html#method-i-throw

поймать документы: https://ruby-doc.org/core-2.6.3/Kernel.html#method-i-catch