设计每次限制每个用户一个会话

时间:2011-08-15 18:23:17

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

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

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

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

在application 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中覆盖Devise Sessions控制器:

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

7 个答案:

答案 0 :(得分:30)

这个宝石效果很好: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实际通知用户他将在登录前杀死当前会话。

答案 1 :(得分:10)

你不能这样做。

  • 您可以控制用户的IP地址,这样您就可以防止用户同时存在两个IP。您可以绑定登录和IP。您可以尝试通过IP检查城市和其他地理位置数据以阻止用户。
  • 您可以设置Cookie来控制其他内容。

但这一切都不能保证只有一个用户使用此登录,并且来自世界各地的那105个IP不属于只有一个使用Proxy或其他任何用户的唯一用户。

最后一点:你永远不需要在互联网上这样做。

<强> UPD

  

但是,我要问的是限制多个用户同时使用同一个帐户我觉得应该可以

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

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

答案 2 :(得分: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

的迁移添加字符串字段

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

这不是最干净的方式,但绝对有效。

答案 3 :(得分:2)

就在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 

答案 4 :(得分:1)

我发现原始帖子中的解决方案并不适合我。我希望第一个用户退出并显示登录页面。此外,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操作。在我的应用程序中,每个页面都有一个“退出”状态。按钮,通过设计会话销毁客户端销毁行动。当在网页中触发websocket响应时,它只需单击此按钮 - 调用注销逻辑,并向第一个用户显示登录页面。

此解决方案也不需要skip_before_filter :check_concurrent_session和模型login_token,因为它会在不带偏见的情况下触发强制注销。

对于记录,devise_security_extension似乎也提供了执行此操作的功能。它还会向第一个用户发出适当的警告,警告发生的事情(我还没弄明白该怎么做)。

答案 5 :(得分:0)

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

答案 6 :(得分:0)

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