【问题标题】:Devise limit one session per user at a time设计限制每个用户一次会话
【发布时间】:2011-10-27 11:44:55
【问题描述】:

我的应用正在使用 Rails 3.0.4 和 Devise 1.1.7。

我正在寻找一种方法来阻止用户共享帐户,因为该应用是基于订阅的服务。我一直在寻找一个多星期,但我仍然不知道如何实施解决方案。我希望有人已经实施了解决方案,并可以为我指明正确的方向。

解决方案(感谢大家的回答和见解!)

在应用程序controller.rb中

before_filter :check_concurrent_session

def check_concurrent_session
  if is_already_logged_in?
    sign_out_and_redirect(current_user)
  end
end

def is_already_logged_in?
  current_user && !(session[:token] == current_user.login_token)
end

在覆盖设计会话控制器的 session_controller 中:

skip_before_filter :check_concurrent_session

def create
  super
  set_login_token
end

private
def set_login_token
  token = Devise.friendly_token
  session[:token] = token
  current_user.login_token = token
  current_user.save
end

迁移中的 AddLoginTokenToUsers

def self.up
  change_table "users" do |t|
    t.string "login_token"
  end
end

def self.down
  change_table "users" do |t|
    t.remove "login_token"
  end
end

【问题讨论】:

  • 这在开发和登台上效果很好,但在生产上它让我的登录过程总是导致立即退出。我总是在登录后将用户重定向到他们在上一次会话期间所在的最后一页。有任何想法吗?奇怪的是,我的暂存环境和生产环境是相同的 Heroku rails 3.2/PostgreSQL 堆栈!

标签: ruby-on-rails ruby-on-rails-3 session devise


【解决方案1】:

这个 gem 很好用:https://github.com/devise-security/devise-security

添加到 Gemfile

gem 'devise-security'

捆绑安装后

rails generate devise_security:install

然后运行

rails g migration AddSessionLimitableToUsers unique_session_id

编辑迁移文件

class AddSessionLimitableToUsers < ActiveRecord::Migration
  def change
    add_column :users, :unique_session_id, :string, limit: 20
  end
end

然后运行

rake db:migrate

编辑您的 app/models/user.rb 文件

class User < ActiveRecord::Base
  devise :session_limitable # other devise options
  ... rest of file ...
end

完成。现在从另一个浏览器登录将终止任何以前的会话。 gem 在登录之前通知用户他即将终止当前会话。

【讨论】:

  • 它看起来有这个功能,但在我写这篇文章时,它与 Rails 4 和 Ruby 2.0 不兼容
  • 在 Rails 4.2 && ruby​​ 2.2.0 上为我工作
  • 适用于 Rails 5.0.2 和 Ruby 2.3.4。我想在新登录后刷新之前的登录页面,如何实现?
  • @TahaRushain 你不会使用session_limitable gem 来刷新之前的会话。如果您从新浏览器访问站点,您能否详细说明为什么要刷新先前的会话?需要更多细节才能正确回答您的问题。
  • @TahaRushain 感谢您的澄清。一个好的用户体验是令人钦佩的。在 AJAX 的帮助下,只需每 n 分钟轮询一个端点。如果您收到 200 OK,您仍处于登录状态。如果您收到 301 或 302 重定向,则闪烁一条消息或在旧浏览器中启动重定向。
【解决方案2】:

你做不到。

  • 您可以控制用户的 IP 地址,这样您就可以防止同时来自两个 IP 的用户出现。并且您可以绑定登录名和IP。您可以尝试通过IP查看城市和其他地理位置数据来屏蔽用户。
  • 您可以设置 cookie 来控制其他内容。

但是这些都不能保证只有一个用户使用这个登录名,并且来自世界各地的 105 个 IP 不只属于一个唯一用户,它使用代理或其他方式。

最后一点:您在互联网上永远不需要这个。

UPD

但是,我要问的是限制多个用户同时使用同一个帐户,我认为这是可能的

因此您可以存储一些令牌,其中将包含一些加密数据:IP + 秘密字符串 + 用户代理 + 用户浏览器版本 + 用户操作系统 + 任何其他个人信息:encrypt(IP + "some secret string" + request.user_agent + ...)。然后您可以使用该令牌设置会话或 cookie。对于每个请求,您都可以获取它:如果用户相同?他是否使用相同的浏览器和来自相同操作系统等的相同浏览器版本?

您还可以使用动态令牌:每次请求都更改令牌,因此每个会话只有一个用户可以使用系统,因为每个请求令牌都会更改,只要他的令牌过期,另一个用户将被注销。

【讨论】:

  • 分享我同意的帐户是不可能停止的。但是,我要问的是关于限制多个用户同时使用同一个帐户,我认为这是可能的。
  • 感谢您更新 fl00rs。我现在使用一个简单的令牌,直到我得到它的工作,但我感谢你的详细回复。
  • 你永远做不到 == 你可以做到。这里是双重否定。
  • @KennyMeyer 不仅如此,这意味着不可能做到这一点:)
  • 不知道你现在对这件事是否还有同样的看法@fl00r ?
【解决方案3】:

这就是我解决重复会话问题的方法。

routes.rb

  devise_for :users, :controllers => { :sessions => "my_sessions" }

my_sessions 控制器

class MySessionsController < Devise::SessionsController
  skip_before_filter :check_concurrent_session

  def create
    super
    set_login_token
  end

  private
  def set_login_token
    token = Devise.friendly_token
    session[:token] = token
    current_user.login_token = token
    current_user.save(validate: false)
  end
end

application_controller

  def check_concurrent_session
    if duplicate_session?
      sign_out_and_redirect(current_user)
      flash[:notice] = "Duplicate Login Detected"
    end
  end

  def duplicate_session?
    user_signed_in? && (current_user.login_token != session[:token])
  end

用户模型 通过名为login_token的迁移添加字符串字段

这会覆盖默认的设计会话控制器,但也继承自它。在新会话中,会创建登录会话令牌并将其存储在 User 模型上的 login_token 中。在应用程序控制器中,我们调用check_concurrent_session,它在调用duplicate_session? 函数后注销并重定向current_user

这不是最干净的方法,但它确实有效。

【讨论】:

    【解决方案4】:

    就在 Devise 中实际实现它而言,将其添加到您的 User.rb 模型中。 这样的事情会自动将它们注销(未经测试)。

      def token_valid?
         # Use fl00rs method of setting the token
         session[:token] == cookies[:token]
      end
    
      ## Monkey Patch Devise methods ##
      def active_for_authentication? 
        super && token_valid?
      end 
      def inactive_message 
       token_valid? ? super : "You are sharing your account." 
      end 
    

    【讨论】:

    • 您好,感谢您的回复。我试图充分了解您的解决方案与地板相结合。我没有找到 active_for_authentication?但我确实发现活跃?我相信这有同样的目的。我有点迷失 cookie[:token] 发挥作用的地方?您只是在 cookie 中设置令牌还是将其存储在数据库中。我更新了我的问题以反映我目前的进展。谢谢。
    【解决方案5】:

    我发现原始帖子中的解决方案并不适合我。我希望第一个用户退出并显示登录页面。此外,sign_out_and_redirect(current_user) 方法似乎没有按我预期的方式工作。在该解决方案中使用 SessionsController 覆盖,我将其修改为使用 websockets,如下所示:

    def create
      super
      force_logout
    end
    
    private
    def force_logout
        logout_subscribe_address = "signout_subscribe_response_#{current_user[:id]}"
        logout_subscribe_resp = {:message => "#{logout_subscribe_address }: #{current_user[:email]} signed out."}
        WebsocketRails[:signout_subscribe].trigger(signout_subscribe_address, signout_subscribe_resp)
      end
    end
    

    确保所有网页都订阅了登出通道并将其绑定到相同的logout_subscribe_address 操作。在我的应用程序中,每个页面还有一个“退出”按钮,该按钮通过设计会话 Destroy 操作退出客户端。当 web 页面中触发 websocket 响应时,它只需单击此按钮 - 将调用注销逻辑并为第一个用户呈现登录页面。

    此解决方案也不需要skip_before_filter :check_concurrent_session 和模型login_token,因为它会触发强制注销而没有偏见。

    作为记录,devise_security_extension 似乎也提供了执行此操作的功能。它还会发出适当的警报,警告第一个用户发生的事情(我还没有弄清楚如何做到这一点)。

    【讨论】:

      【解决方案6】:

      跟踪每个用户使用的唯一 IP。时不时地对这些 IP 进行分析 - 如果单个帐户同时从不同国家的不同 ISP 登录,共享将是显而易见的。请注意,仅仅拥有不同的 IP 并不足以将其视为共享 - 一些 ISP 使用循环代理,因此每个命中必然是不同的 IP。

      【讨论】:

      • 比起跨国家/地区,我更担心用户在组织内共享帐户。如果共享帐户由同一网络上的多人共享,uniq IP 解决方案是否可行?
      • 可能,除非他们有一个 NAT 网关,这会使所有用户出现在一个(或很少的 IP)下。
      【解决方案7】:

      虽然您无法可靠地阻止用户共享帐户,但您可以做的(我认为)是阻止多个用户同时登录到同一个帐户。不确定这是否足以满足您的业务模型,但它确实解决了其他答案中讨论的许多问题。我已经实现了一些目前处于测试阶段并且似乎运行良好的东西 - 有一些注释here

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-06-10
        • 2023-04-06
        • 2015-02-17
        • 1970-01-01
        • 2014-07-03
        • 2018-10-06
        • 1970-01-01
        • 2017-04-25
        相关资源
        最近更新 更多