11

当有人无法登录我的应用程序时,我需要写一个日志(以跟踪暴力尝试)。我还决定记录成功的身份验证。所以我创建了一个SessionsController < Devise::SessionsController并尝试像这样覆盖 session#create 方法:https ://gist.github.com/3884693

第一部分工作得很好,但是当身份验证失败时,rails 会抛出某种异常并且永远不会到达 if 语句。所以我不知道该怎么办。

4

6 回答 6

26

这个答案对以前的 SO 问题 - 设计:注册登录尝试有答案。

设计控制器中的创建操作调用warden.authenticate!,它尝试使用提供的参数对用户进行身份验证。如果身份验证失败,则进行身份验证!将调用设计失败应用程序,然后运行 ​​SessionsController#new 操作。请注意,如果身份验证失败,您为创建操作设置的任何过滤器都不会运行。

因此解决方案是在新操作之后添加一个过滤器,该过滤器检查 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
于 2012-10-13T21:30:59.513 回答
8

Prakash 的回答很有帮助,但依靠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

如果您希望挂钩 Warden 的回调,请查看Graeme 的回答(使用 Warden 实现设计)。

于 2016-08-18T12:12:47.110 回答
7

我有同样的问题,但无法使用,"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_回调似乎也有变体。

于 2015-10-20T07:30:08.300 回答
1

For logout logging, you need to catch the destroy event, so add the following to the Session controller (from the above answer):

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
于 2013-10-19T19:34:40.590 回答
1

我找到了另一种方法来执行此操作,例如,如果您想在登录失败时显示自定义消息。

在我的工作中,如果登录失败,我们会检查活动状态(自定义逻辑)并显示一条消息,无论登录是否正确。

在调试了一下并阅读了warden docs之后,我现在知道了: Warden 执行 a throw(:warden, opts),因此,根据 ruby​​ 文档,throw必须在catch块内捕获 a 。

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

于 2019-07-05T14:30:45.227 回答
0

基于 Prakash Murty 的回答,我认为这个答案中的方法(https://stackoverflow.com/a/34816998/891359)是记录成功登录尝试的更简洁的方法。与调用 super 不同,Devise 提供了一种yield在视图呈现之前传递块的方法。

所以不要这样做:

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

这样做更清洁:

class SessionsController < Devise::SessionsController
  def create
    super do |user|
      ::Rails.logger.info "\n***\nSuccessful login with email_id : #{user.email}\n***\n"
    end
  end
end
于 2020-03-10T07:07:00.187 回答