【问题标题】:Devise - Make a request without resetting the countdown until a user is logged out due to inactivity设计 - 发出请求而不重置倒计时,直到用户因不活动而注销
【发布时间】:2013-07-22 15:32:25
【问题描述】:

我正在使用 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_logouthas_user_timed_out 中的重置?

【问题讨论】:

    标签: ruby-on-rails devise


    【解决方案1】:

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

    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 内部使用的哈希值,以决定是否更新它存储的值以跟踪用户上次活动的时间。具体来说,这是我们正在与之交互的设计代码 (link):

    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'] 不是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
    

    以上所有内容都是我需要更改的内容才能回答我的问题。不过,我还需要进行一些其他更改才能让我的代码按我的意愿工作,所以我也会在这里记录它们。

    正确使用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_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? 的文档。当您输入用户上次活动的时间时,它会检查用户是否超时,不是,当您输入当前时间时。我们需要做的改变很简单:我们需要在user_session 哈希中传递时间,而不是传递Time.now

      def has_user_timed_out
        @has_timed_out = (!current_user) or (current_user.timedout? (user_session["last_request_at"]))
      end
    

    完成这三项更改后,我的控制器就会按照我的预期行事。

    【讨论】:

    • 很好的答案,感谢代码示例!将此代码与 rails 4.2.6 和设计 3.5.6 一起使用,我有几个错误需要修复; NoMethodError Exception: undefined method -@'` 已通过将 @time_left = Devise.timeout_in - (Time.now - user_session["last_request_at"]).round 更改为 @time_left = Devise.timeout_in - (Time.now - user_session["last_request_at"]).to_i 来修复
    • 并且通过将@has_timed_out = (!current_user) or (current_user.timedout? (user_session["last_request_at"]))行更改为@has_timed_out = (!current_user) or (current_user.timedout? (Time.at(user_session["last_request_at"])))来修复错误ArgumentError Exception: comparison of Fixnum with ActiveSupport::TimeWithZone failed
    • 如何在设计超时前向客户端发送警报消息?
    • @CharanKumarBorra 在我们的解决方案中,我们编写了一些客户端 JavaScript,大约每分钟 ping 一次服务器,以查看用户在注销之前可以等待多长时间。剩下大约 1 分钟后,该代码会向用户显示一条消息。
    • 你能分享一下客户端的Javascript代码吗?这样我就可以尝试将它用于我的应用程序。
    【解决方案2】:

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

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

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

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

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

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

    【讨论】:

    • 我给你赏金,因为你的回答给了我需要弄清楚我在做什么的推动力。我接受我的回答是因为我认为,由于我提供了一个代码示例,准确地显示了如何处理我的问题,它对未来的读者会更有用。感谢您的帮助! =D
    【解决方案3】:

    添加request.env["devise.skip_trackable"] = true 操作的两个主要答案最初对我不起作用。

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

    authenticate :user do
      resources :some_resources
    end
    

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

    【讨论】:

      【解决方案4】:

      在 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
      

      所选答案引用的Devise code 有一个新部分:

      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

      【讨论】:

        【解决方案5】:

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

        def check_time_until_logout
          @time_left = Devise.timeout_in.seconds - (Time.now.to_i - user_session["last_request_at"]).round
        end
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-04-05
          • 2023-03-13
          • 1970-01-01
          • 2017-11-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多