20

我正在使用 Devise 开发一个 RoR 应用程序。我想让客户端向服务器发送请求,以查看客户端上的用户由于不活动而自动注销(使用Timeoutable模块)还剩多少时间。我不希望此请求导致 Devise 重置倒计时,直到用户注销为止。我该如何配置?

这是我现在的代码:

class SessionTimeoutController < ApplicationController
  before_filter :authenticate_user!

  # Calculates the number of seconds until the user is
  # automatically logged out due to inactivity. Unlike most
  # requests, it should not reset the timeout countdown itself.
  def check_time_until_logout
    @time_left = current_user.timeout_in
  end

  # Determines whether the user has been logged out due to
  # inactivity or not. Unlike most requests, it should not reset the
  # timeout countdown itself.
  def has_user_timed_out
    @has_timed_out = current_user.timedout? (Time.now)
  end

  # Resets the clock used to determine whether to log the user out
  # due to inactivity.
  def reset_user_clock
    # Receiving an arbitrary request from a client automatically
    # resets the Devise Timeoutable timer.
    head :ok
  end
end

SessionTimeoutController#reset_user_clock之所以有效,是因为每次 RoR 收到来自经过身份验证的用户的请求时,Timeoutable#timeout_in都会重置为我在 Devise#timeout_in. 如何防止在check_time_until_logoutand中重置has_user_timed_out

4

5 回答 5

27

我最终不得不对我的代码进行一些更改。我将展示我完成后的结果,然后解释它的作用:

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_sessionDevise 为我们提供的哈希,就像句柄current_useruser_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

一旦我进行了这三个更改,我的控制器就会按照我的预期行事。

于 2013-07-26T15:46:02.790 回答
17

当我正确看到这一点时(可能取决于设计版本),设计通过Timeoutable模块处理注销。

这是连接到warden的,它是机架中间件堆栈的一部分。

如果您查看代码,则可以找到以下部分:

unless warden.request.env['devise.skip_trackable']
  warden.session(scope)['last_request_at'] = Time.now.utc
end

所以从我在这里看到的,你应该能够devise.skip_trackable在调用warden中间件之前设置为true。

我认为这里的这个问题解释了如何实际使用它:https ://github.com/plataformatec/devise/issues/953

于 2013-07-24T17:04:07.277 回答
3

预先采取行动的两个主要答案request.env["devise.skip_trackable"] = true最初对我不起作用。

似乎这仅在您authenticate_user!在操作前使用“经典”时才有效,而不是在使用经过身份验证的路由时。如果您在 中使用经过身份验证的路由config/routes.rb,例如:

authenticate :user do
  resources :some_resources
end

前置操作似乎不会及时影响设计链,因此您应该注释掉该authenticate块,在您的 中使用authenticate_user!before 操作,并在特定控制器中app/controllers/application_controller.rb添加一个设置为 true 的前置操作。skip_trackable

于 2016-03-27T06:19:28.033 回答
2

在 Rails 6 和 Devise 4.7.1 中,这对我有用,这要感谢选择和赏金的答案。

# controller

prepend_before_action :skip_trackable, only: :update, if: -> { request.format.js? }

private

def skip_trackable
  request.env["devise.skip_timeout"] = true
  request.env["devise.skip_trackable"] = true
end

所选答案引用的设计代码有一个新部分:

if !env['devise.skip_timeout'] &&
    record.timedout?(last_request_at) &&
    !proxy.remember_me_is_active?(record)
  Devise.sign_out_all_scopes ? proxy.sign_out : proxy.sign_out(scope)
  throw :warden, scope: scope, message: :timeout
end

这也需要设置devise.skip_timeout

于 2021-05-29T00:11:59.363 回答
0

您的剩余时间代码不正确。正确的计算将是:

def check_time_until_logout
  @time_left = Devise.timeout_in.seconds - (Time.now.to_i - user_session["last_request_at"]).round
end
于 2019-05-09T22:43:22.507 回答