Authentication 身份验证失败后设计日志

Authentication 身份验证失败后设计日志,authentication,devise,ruby-on-rails-3.2,Authentication,Devise,Ruby On Rails 3.2,当有人登录我的应用程序失败时,我需要写一个日志(跟踪暴力尝试)。我还决定记录成功的身份验证。 因此,我创建了一个sessioncontroller

当有人登录我的应用程序失败时,我需要写一个日志(跟踪暴力尝试)。我还决定记录成功的身份验证。 因此,我创建了一个sessioncontroller并尝试覆盖sessions#create方法,如下所示:

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

有答案

设备控制器中的创建操作调用warden.authenticate!,它尝试使用提供的参数对用户进行身份验证。如果身份验证失败,则进行身份验证!将调用Desive failure应用程序,然后运行SessionController#new action。注意,如果身份验证失败,则创建操作的任何筛选器都不会运行

因此,解决方案是在新操作之后添加一个过滤器,检查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
对于失败的登录
对于注销日志记录,您需要捕获销毁事件,因此将以下内容添加到会话控制器(从上面的答案):


我有同样的问题,但无法使用
“warden.options”
解决,因为在我的情况下,在重定向到
会话#new
操作之前,这些选项已被清除。在研究了一些我认为过于脆弱的备选方案之后(因为它们涉及扩展一些Desive类和对现有方法进行别名),我最终使用了一些。它对我来说效果更好,因为回调是在当前请求-响应周期内调用的,并且参数都保存在
env
对象中

这些回调被命名,似乎是为了解决此问题和相关问题而设计的。而且它们是有文件记录的

Warden从
Warden-1.2.3
起支持以下回调:

  • 设置用户后的
  • 认证后的
    (用于记录成功登录)
  • 获取之后的
    (设置用户之后的
    别名)
  • 发生故障之前
    (用于记录失败的登录-下面的示例)
  • 获取失败后的
  • 注销前
  • 应请求
每个回调都直接在
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
我希望您可以在退出之前使用
回调来跟踪退出操作,但我还没有测试它。回调的变体似乎也有
prepend

是有帮助的,但依靠
sessioncontroller#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
class LogAuthenticationFailure


查看您是否愿意挂接Warden的回调(Desive是使用Warden实现的)。

我找到了另一种方法,例如,如果您希望,在登录失败时显示自定义消息

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

在调试了一点并阅读了warden文档之后,我现在知道了:warden执行一个
抛出(:warden,opts)
,因此,根据ruby文档,必须在
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
抛出文档:

捕获文档:

基于Prakash Murty的答案,我认为这个答案()中的方法是记录成功登录尝试的更干净的方法。designe提供了一种在渲染视图之前传递
yield
ed的块的方法,而不是调用super

因此,与其这样做,不如:

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 sessioncontroller
这样做比较干净:

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
class sessioncontroller
我还按照上的Desive指南操作了其余的代码。我不得不将其更改为一个符号:选项[:操作]=未经认证的还有一种方法来捕获失败的登录尝试,因为用户试图登录未经确认的帐户?有一点要考虑的是,这种方法还记录访问未经验证的路线…您可以添加另一个筛选器以确保它仅在登录尝试时使用:
如果env.dig(“warden.options”),:action)=“unauthenticated”&&env.dig(“warden.options”),:message)=:invalid
在Rails 5.1中,我收到了
弃用警告:env已弃用,将从Rails 5.1中删除。谢谢您的评论!我将
env
更新为
request.env
per。如果您仍然看到弃用或存在任何其他问题,请告诉我。是否有任何参数传递给
Devise::FailureApp
。换句话说,我能从尝试登录的用户中提取一些信息吗?@bo oz我已经很长时间没有使用此代码了(无法验证),但请尝试深入到request.env?您应该能够获得会话信息。似乎不再需要额外的数据库命中。当在数据库中找不到用户时,
opts[:message]
被设置为
:not\u found\u in\u database
,这太好了。谢谢
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
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