Разработайте ограничение на один сеанс на пользователя за раз - PullRequest
45 голосов
/ 15 августа 2011

Мое приложение использует 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, который переопределяет контроллер 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 ]

30 голосов
/ 09 августа 2013

Этот камень хорошо работает: 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

Готово. Теперь вход в систему из другого браузера уничтожит все предыдущие сеансы. Фактический самоцвет уведомляет пользователя о том, что он собирается завершить текущий сеанс, прежде чем войти в систему.

10 голосов
/ 15 августа 2011

Вы не можете сделать это.

  • Вы можете контролировать IP-адреса пользователя, поэтому вы можете предотвратить присутствие пользователя с двух IP одновременно.И вы можете связать логин и IP.Вы можете попытаться проверить города и другие данные геолокации через IP, чтобы заблокировать пользователя.
  • Вы можете установить куки для управления чем-то другим.

Но все это не гарантирует, что только один пользовательиспользует этот логин, и что эти 105 IP со всего мира не принадлежат только одному уникальному пользователю, который использует Прокси или что-то еще.

И последнее: вам никогда не нужно это в Интернете.

UPD

Однако я спрашиваю об ограничении одновременного использования несколькими пользователями одной и той же учетной записи, что, на мой взгляд, должно быть возможным

Таким образом, вы можете хранить токен, который будет содержать некоторые зашифрованные данные: IP + секретная строка + пользовательский агент + версия браузера пользователя + пользовательская ОС + любая другая личная информация: encrypt(IP + "some secret string" + request.user_agent + ...).И тогда вы можете установить сеанс или куки с этим токеном.И с каждым запросом вы можете получить его: если пользователь тот же?Использует ли он тот же браузер и ту же версию браузера из той же ОС и т. Д.

Также вы можете использовать динамические токены: вы меняете токен каждый запрос, так что только один пользователь может использовать систему на сеанс, потому что каждый запрос токенбудет изменен, другой пользователь выйдет из системы по истечении срока действия его токена.

3 голосов
/ 23 сентября 2014

Вот так я решил проблему с дублированием сессии.

rout.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 по умолчаниюКонтроллер сеанса, но также наследуется от него.В новом сеансе токен сеанса входа создается и сохраняется в login_token в модели User.В контроллере приложения мы вызываем check_concurrent_session, который выписывает и перенаправляет current_user после вызова функции duplicate_session?.

Это не самый чистый способ сделать это, но он определенно работает.

2 голосов
/ 16 августа 2011

Что касается фактической реализации в 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 
1 голос
/ 07 июня 2014

Я обнаружил, что решение в оригинальной публикации не совсем работает для меня.Я хотел, чтобы первый пользователь вышел из системы и представил страницу входа.Кроме того, метод sign_out_and_redirect(current_user), похоже, не работает так, как я ожидал.Используя переопределение SessionsController в этом решении, я изменил его для использования веб-сокетов следующим образом:

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 сеанса devise.Когда на веб-странице запускается ответ веб-сокета, он просто нажимает эту кнопку - вызывается логика выхода и первый пользователь получает страницу входа.

Это решение также не требует skip_before_filter :check_concurrent_sessionи модель login_token, поскольку она запускает принудительный выход без ущерба.

Для записи, devise_security_extension, по-видимому, также предоставляет функциональные возможности для этого.Также выдает соответствующее предупреждение, предупреждающее первого пользователя о том, что произошло (я пока не выяснил, как это сделать).

0 голосов
/ 16 августа 2011

Хотя вы не можете надежно предотвратить совместное использование учетной записи пользователями, я думаю, что вы можете предотвратить одновременный вход нескольких пользователей в одну и ту же учетную запись.Не уверен, что этого достаточно для вашей бизнес-модели, но он обходит множество проблем, обсуждаемых в других ответах.Я реализовал кое-что, что в настоящее время находится в бета-версии и, кажется, работает достаточно хорошо - здесь есть некоторые примечания

0 голосов
/ 15 августа 2011

Отслеживайте уникальные IP-адреса, используемые для каждого пользователя. Время от времени проводите анализ этих IP-адресов - совместное использование было бы очевидным, если бы к одной учетной записи одновременно подключались разные провайдеры в разных странах. Обратите внимание, что просто наличие другого IP-адреса не является достаточным основанием для того, чтобы считать его общим - некоторые интернет-провайдеры используют прокси-серверы циклического перебора, поэтому для каждого попадания обязательно будет свой IP-адрес.

...