设计 - 在用户因不活动而退出之前,在不重置倒计时的情况下发出请求

Kev*_*ica 19 ruby-on-rails devise

我正在和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
Run Code Online (Sandbox Code Playgroud)

SessionTimeoutController#reset_user_clock因为RoR每次收到来自经过身份验证的用户的请求,Timeoutable#timeout_in都会重置为我已配置的任何内容 Devise#timeout_in.如何防止在复位check_time_until_logouthas_user_timed_out

Kev*_*ica 24

我最终不得不对我的代码进行一些更改.我会在完成后展示我最终得到的东西,然后解释它的作用:

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
Run Code Online (Sandbox Code Playgroud)

这些是我所做的改变:

运用 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
Run Code Online (Sandbox Code Playgroud)

此代码更改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
Run Code Online (Sandbox Code Playgroud)

(请注意,每次Rails处理来自客户端的请求时都会执行此代码.)

接近尾声的这些线对我们来说很有趣:

    unless env['devise.skip_trackable']
      warden.session(scope)['last_request_at'] = Time.now.utc
    end
Run Code Online (Sandbox Code Playgroud)

这是"重置"倒计时的代码,直到用户因不活动而退出.它只env['devise.skip_trackable']在不执行时执行true,因此我们需要在Devise处理用户请求之前更改该值.

为此,我们告诉Rails env['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
Run Code Online (Sandbox Code Playgroud)

高于这一点的一切都是我需要改变来回答我的问题.我需要做一些其他的改变才能让我的代码按照我的意愿工作,所以我也会在这里记录它们.

Timeoutable正确使用

我误读了关于Timeoutable模块的文档,所以我的问题中的代码还有其他几个问题.

首先,我的check_time_until_logout方法总是返回相同的值.这是我所采取行动的错误版本:

def check_time_until_logout
  @time_left = current_user.timeout_in
end
Run Code Online (Sandbox Code Playgroud)

我认为这Timeoutable#timeout_in将返回用户自动注销之前的时间量.相反,它返回Devise配置为在用户注销之前等待的时间量.我们需要计算用户离开的时间.

为了计算这一点,我们需要知道用户上次具有Devise识别的活动的时间.这段代码来自我们上面看到的Devise源代码,用于确定用户上次活动的时间:

    last_request_at = warden.session(scope)['last_request_at']
Run Code Online (Sandbox Code Playgroud)

我们需要获取返回的对象的句柄warden.session(scope).它看起来像user_session乱码,它设计为我们提供了类似的手柄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
Run Code Online (Sandbox Code Playgroud)

我也误读了文档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
Run Code Online (Sandbox Code Playgroud)

一旦我做了这三个更改,我的控制器就按照我的预期方式行事.

  • 很好的答案,谢谢代码示例!将此代码与rails 4.2.6一起使用,并设计3.5.6我有几个错误要修复; `NoMethodError异常:未定义的方法` - @'`通过改变`@time_left = Devise.timeout_in - (Time.now - user_session ["last_request_at"]).round`到`@time_left = Devise.timeout_in - (时间. now - user_session ["last_request_at"]).to_i` (4认同)
  • 并且错误`ArgumentError Exception: compare of Fixnum with ActiveSupport::TimeWithZone failed` 通过将行`@has_timed_out = (!current_user) or (current_user.timedout? (user_session["last_request_at"]))` 更改为`@ has_timed_out = (!current_user) 或 (current_user.timedout? (Time.at(user_session["last_request_at"])))` (2认同)

pho*_*oet 17

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

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

如果你看代码,你可以找到这个部分:

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

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

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