我最终不得不对我的代码进行一些更改。我将展示我完成后的结果,然后解释它的作用:
class SessionTimeoutController < ApplicationController
# These are what prevent check_time_until_logout and
# reset_user_clock from resetting users' Timeoutable
# Devise "timers"
prepend_before_action :skip_timeout, only: [:check_time_until_logout, :has_user_timed_out]
def skip_timeout
request.env["devise.skip_trackable"] = true
end
skip_before_filter :authenticate_user!, only: [:has_user_timed_out]
def check_time_until_logout
@time_left = Devise.timeout_in - (Time.now - user_session["last_request_at"]).round
end
def has_user_timed_out
@has_timed_out = (!current_user) or (current_user.timedout? (user_session["last_request_at"]))
end
def reset_user_clock
# Receiving an arbitrary request from a client automatically
# resets the Devise Timeoutable timer.
head :ok
end
end
这些是我所做的更改:
使用env["devise.skip_trackable"]
这是防止 Devise 重置由于不活动而注销用户之前等待多长时间的代码:
prepend_before_action :skip_timeout, only: [:check_time_until_logout, :has_user_timed_out]
def skip_timeout
request.env["devise.skip_trackable"] = true
end
此代码更改 Devise 内部使用的哈希值,以决定是否更新其存储的值以跟踪用户上次活动的时间。具体来说,这是我们正在与之交互的设计代码(链接):
Warden::Manager.after_set_user do |record, warden, options|
scope = options[:scope]
env = warden.request.env
if record && record.respond_to?(:timedout?) && warden.authenticated?(scope) && options[:store] != false
last_request_at = warden.session(scope)['last_request_at']
if record.timedout?(last_request_at) && !env['devise.skip_timeout']
warden.logout(scope)
if record.respond_to?(:expire_auth_token_on_timeout) && record.expire_auth_token_on_timeout
record.reset_authentication_token!
end
throw :warden, :scope => scope, :message => :timeout
end
unless env['devise.skip_trackable']
warden.session(scope)['last_request_at'] = Time.now.utc
end
end
end
(请注意,每次 Rails 处理来自客户端的请求时,都会执行此代码。)
接近结尾的这些行对我们来说很有趣:
unless env['devise.skip_trackable']
warden.session(scope)['last_request_at'] = Time.now.utc
end
这是“重置”倒计时的代码,直到用户由于不活动而退出。它仅在env['devise.skip_trackable']
is not时执行true
,因此我们需要在 Devise 处理用户请求之前更改该值。
为此,我们告诉 Railsenv['devise.skip_trackable']
在它做任何其他事情之前改变它的值。同样,从我的最终代码:
prepend_before_action :skip_timeout, only: [:check_time_until_logout, :has_user_timed_out]
def skip_timeout
request.env["devise.skip_trackable"] = true
end
高于这一点的所有内容都是我需要更改才能回答我的问题。不过,我还需要进行一些其他更改才能让我的代码按我的意愿工作,所以我也会在这里记录它们。
Timeoutable
正确使用
我误读了有关该Timeoutable
模块的文档,因此我的问题中的代码还有其他一些问题。
首先,我的check_time_until_logout
方法总是返回相同的值。这是我所采取的行动的错误版本:
def check_time_until_logout
@time_left = current_user.timeout_in
end
我认为这Timeoutable#timeout_in
会返回用户自动注销之前的时间。相反,它返回 Devise 配置为在注销用户之前等待的时间量。我们需要计算用户离开自己的时间。
为了计算这一点,我们需要知道用户上次进行 Devise 识别的活动是什么时候。这段代码,来自我们上面看到的设计源代码,确定了用户上次活动的时间:
last_request_at = warden.session(scope)['last_request_at']
我们需要获取由 . 返回的对象的句柄warden.session(scope)
。它看起来像是user_session
Devise 为我们提供的哈希,就像句柄current_user
和user_signed_in?
,就是那个对象。
使用user_session
哈希并计算我们自己的剩余时间,该check_time_until_logout
方法变为
def check_time_until_logout
@time_left = Devise.timeout_in - (Time.now - user_session["last_request_at"]).round
end
我还误读了Timeoutable#timedout?
. 它检查用户是否在您输入用户上次活动的时间时超时,而不是在您输入当前时间时。我们需要做的改变很简单:Time.now
我们需要在user_session
哈希中传递时间,而不是传入 :
def has_user_timed_out
@has_timed_out = (!current_user) or (current_user.timedout? (user_session["last_request_at"]))
end
一旦我进行了这三个更改,我的控制器就会按照我的预期行事。