Ruby-on-rails 设计每个用户一次限制一个会话

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7068919/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 01:45:52  来源:igfitidea点击:

Devise limit one session per user at a time

ruby-on-railsruby-on-rails-3sessiondevise

提问by John

My app is using Rails 3.0.4 and Devise 1.1.7.

我的应用程序使用 Rails 3.0.4 和 Devise 1.1.7。

I'm looking for a way to prevent users from sharing accounts as the app is a subscription based service. I've been searching for over a week, and I still don't know how to implement a solution. I'm hoping someone has implemented a solution and can point me in the right direction.

我正在寻找一种方法来防止用户共享帐户,因为该应用程序是基于订阅的服务。找了一个多星期了,还是不知道怎么实现。我希望有人已经实施了一个解决方案,并且可以为我指明正确的方向。

Solution(Thank you everyone for your answers and insight!)

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

In application controller.rb

在应用程序控制器.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

In session_controller that overrides Devise Sessions controller:

在覆盖设计会话控制器的 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

In migration AddLoginTokenToUsers

在迁移中 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

采纳答案by fl00r

You can't do it.

你不能这样做。

  • You can control IP addresses of user, so you can prevent presence of user from two IP at a time. ANd you can bind login and IP. You can try to check cities and other geolocation data through IP to block user.
  • You can set cookies to control something else.
  • 您可以控制用户的 IP 地址,因此您可以防止用户同时来自两个 IP。并且您可以绑定登录名和IP。您可以尝试通过 IP 检查城市和其他地理位置数据以阻止用户。
  • 您可以设置 cookie 来控制其他内容。

But none of this will guarantee that only one user uses this login, and that those 105 IP from all over the world doesn't belong to only one unique user, which uses Proxy or whatever.

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

And the last: you never need this in the Internet.

最后一点:您在 Internet 中永远不需要这个。

UPD

UPD

However, what I'm asking is about limiting multiple users from using the same account simultaneously which I feel should be possible

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

So you can store some token, that will contain some encrypted data: IP + secret string + user agent + user browser version + user OS + any other personal info: encrypt(IP + "some secret string" + request.user_agent + ...). And then you can set a session or cookie with that token. And with each request you can fetch it: if user is the same? Is he using the same browser and the same browser version from the same OS etc.

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

Also you can use dynamic tokens: you change token each request, so only one user could use system per session, because each request token will be changed, another user will be logged out as far as his token will be expired.

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

回答by scarver2

This gem works well: https://github.com/devise-security/devise-security

这个 gem 运行良好:https: //github.com/devise-security/devise-security

Add to Gemfile

添加到 Gemfile

gem 'devise-security'

after bundle install

捆绑安装后

rails generate devise_security:install

Then run

然后运行

rails g migration AddSessionLimitableToUsers unique_session_id

Edit the migration file

编辑迁移文件

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

Then run

然后运行

rake db:migrate

Edit your app/models/user.rb file

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

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

Done. Now logging in from another browser will kill any previous sessions. The gem actual notifies the user that he is about to kill a current session before logging in.

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

回答by nulltek

This is how I solved the duplicate session problem.

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

routes.rb

路由文件

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

my_sessions controller

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

User modelAdd a string field via a migration named login_token

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

This overrides the default Devise Session controller but inherits from it as well. On a new session a login session token is created and stored in login_tokenon the User model. In the application controller we call check_concurrent_sessionwhich signs out and redirects the current_userafter calling the duplicate_session?function.

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

It's not the cleanest way to go about it, but it definitely works.

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

回答by Dex

As far as actually implementing it in Devise, add this to your User.rb model. Something like this will log them out automatically (untested).

至于在设计中实际实现它,请将其添加到您的 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 

回答by davidm

I found that the solution in the original posting did not quite work for me. I wanted the first user to be logged out and a log-in page presented. Also, the sign_out_and_redirect(current_user)method does not seem to work the way I would expect. Using the SessionsController override in that solution I modified it to use websockets as follows:

我发现原始帖子中的解决方案对我来说不太适用。我希望第一个用户注销并显示登录页面。此外,该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

Make sure that all web pages subscribe to the signout channel and bind it to the same logout_subscribe_addressaction. In my application, each page also has a 'sign out' button, which signs out the client via the devise session Destroy action. When the websocket response is triggered in the web page, it simply clicks this button - the signout logic is invoked and the first user is presented with the sign in page.

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

This solution also does not require the skip_before_filter :check_concurrent_sessionand the model login_tokensince it triggers the forced logout without prejudice.

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

For the record, the devise_security_extensionappears to provide the functionality to do this as well. It also puts up an appropriate alert warning the first user about what has happened (I haven't figured out how to do that yet).

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

回答by chrispanda

While you can't reliably prevent users from sharing an account, what you can do (I think) is prevent more than one user being logged on at the same time to the same account. Not sure if this is sufficient for your business model, but it does get around a lot of the problems discussed in the other answers. I've implemented something that is currently in beta and seems to work reasonably well - there are some notes here

虽然您无法可靠地阻止用户共享帐户,但您可以做的(我认为)是防止多个用户同时登录到同一个帐户。不确定这对您的商业模式是否足够,但它确实解决了其他答案中讨论的许多问题。我已经实现的东西,目前处于测试阶段,似乎工作相当好-有一些注意事项这里

回答by Marc B

Keep track of uniq IPs used per user. Now and then, run an analysis on those IPs - sharing would be obvious if a single account has simultaneous logins from different ISPs in different countries. Note that simply having a different IP is not sufficient grounds to consider it shared - some ISPs use round-robin proxies, so each hit would necessarily be a different IP.

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