当有人试图登录我的应用程序(跟踪暴力尝试)时,我需要写一个日志。我还决定记录成功的身份验证。 所以我创建了一个 SessionsController<设计:: SessionsController 并尝试覆盖会话#create方法:https://gist.github.com/3884693
第一部分工作正常,但是当auth failes rails抛出某种异常并且永远不会到达if语句。所以我不知道该怎么做。
答案 0 :(得分:18)
This answer to a previous SO question - Devise: Registering log in attempts有答案。
设计控制器中的创建操作调用warden.authenticate !,它尝试使用提供的参数对用户进行身份验证。如果验证失败,则验证!将调用设计失败应用程序,然后运行SessionsController #new动作。请注意,如果身份验证失败,您对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
答案 1 :(得分:4)
我有同样的问题,但无法使用"warden.options"
解决问题,因为在我的情况下,这些问题在重定向到sessions#new
操作之前已被清除。在研究了一些我认为太脆弱的替代方案(因为它们涉及扩展一些Devise类并使现有方法混淆)之后,我使用了一些callbacks provided by 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_
个回调变体。
答案 2 :(得分:4)
Prakash's answer很有帮助,但依靠SessionsController#new
作为副作用来运行并不理想。我相信这更清洁了:
class LogAuthenticationFailure < Devise::FailureApp
def respond
if 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的回调(使用Warden实现设计),请查看Graeme's answer。
答案 3 :(得分:1)
对于注销日志记录,您需要捕获destroy事件,因此将以下内容添加到Session控制器(来自上面的答案):
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
答案 4 :(得分:0)
例如,如果您想要登录失败,则显示自定义消息,这是我找到的另一种方法。
在我的工作中,如果登录失败,无论登录是否正确,我们都会检查活动状态(自定义逻辑)并显示一条消息。
经过一点调试并阅读了管理员文档后,我现在知道了:管理员执行throw(:warden, opts)
,因此根据ruby docs,必须在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
答案 5 :(得分: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