我的应用正在使用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
答案 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)
你不能这样做。
但这一切都不能保证只有一个用户使用此登录,并且来自世界各地的那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