Ruby on Rails-设计限制每个用户一个会话一次的会话时间

我的应用程序正在使用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

在覆盖Devise Sessions控制器的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
John asked 2020-06-26T18:03:47Z
7个解决方案
31 votes

这个宝石工作得很好:[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实际上会在登录之前通知用户他即将终止当前会话。

scarver2 answered 2020-06-26T18:05:24Z
10 votes

你做不到

  • 您可以控制用户的IP地址,因此可以一次阻止来自两个IP的用户的存在。 您还可以绑定登录名和IP。 您可以尝试通过IP检查城市和其他地理位置数据,以阻止用户。
  • 您可以设置Cookie来控制其他内容。

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

最后一点:您从不需要Internet。

UPD

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

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

您还可以使用动态令牌:您可以更改每个请求的令牌,因此每个会话只能使用一个用户,因为每个请求令牌都将被更改,而另一个用户将被注销,直到其令牌过期为止。

fl00r answered 2020-06-26T18:04:33Z
3 votes

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

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,退出并在调用duplicate_session?函数后重定向current_user

这不是最干净的方法,但是绝对可以。

nulltek answered 2020-06-26T18:06:10Z
2 votes

至于在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 
Dex answered 2020-06-26T18:06:31Z
1 votes

我发现原始帖子中的解决方案对我而言不太有效。 我希望第一个用户注销并显示登录页面。 另外,devise_security_extension方法似乎无法按我期望的方式工作。 在该解决方案中使用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

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

此解决方案也不需要devise_security_extension和型号login_token,因为它会在不影响用户的情况下触发强制注销。

记录下来,devise_security_extension似乎也提供了执行此操作的功能。 它还会发出一个适当的警报,警告第一个用户发生了什么事情(我还没有弄清楚该怎么做)。

davidm answered 2020-06-26T18:07:05Z
0 votes

跟踪每个用户使用的uniq IP。 时不时地对这些IP进行分析-如果单个帐户同时具有来自不同国家/地区的不同ISP的登录名,则共享将显而易见。 请注意,仅仅拥有一个不同的IP不足以认为它是共享的-一些ISP使用轮询代理,因此每次访问都必然是一个不同的IP。

Marc B answered 2020-06-26T18:07:26Z
0 votes

虽然您不能可靠地阻止用户共享一个帐户,但您可以做的(我认为)是防止多个用户同时登录同一帐户。 不确定这对于您的业务模型是否足够,但是确实解决了其他答案中讨论的许多问题。 我已经实现了目前处于测试阶段的功能,并且看起来工作得相当不错-这里有一些注意事项

chrispanda answered 2020-06-26T18:07:47Z
translate from https://stackoverflow.com:/questions/7068919/devise-limit-one-session-per-user-at-a-time