web-dev-qa-db-fra.com

Concevoir une session par utilisateur à la fois

Mon application utilise Rails 3.0.4 et Devise 1.1.7.

Je cherche un moyen d'empêcher les utilisateurs de partager des comptes car l'application est un service par abonnement. Je recherche depuis plus d'une semaine et je ne sais toujours pas comment implémenter une solution. J'espère que quelqu'un a mis en œuvre une solution et peut m'orienter dans la bonne direction.

Solution (Merci à tous pour vos réponses et vos idées!)

Dans l'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

Dans session_controller qui remplace le contrôleur 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

Dans la migration 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
51
John

Tu ne peux pas le faire.

  • Vous pouvez contrôler les adresses IP de l'utilisateur afin d'empêcher la présence de l'utilisateur à partir de deux IP à la fois. ET vous pouvez lier la connexion et l'IP. Vous pouvez essayer de vérifier les villes et autres données de géolocalisation via IP pour bloquer l'utilisateur.
  • Vous pouvez définir des cookies pour contrôler autre chose.

Mais rien de tout cela ne garantira qu'un seul utilisateur utilise cette connexion, et que ces 105 IP du monde entier n'appartiennent pas à un seul utilisateur unique, qui utilise Proxy ou autre chose.

Et le dernier: vous n'en avez jamais besoin sur Internet.

UPD

Cependant, ce que je demande, c'est d'empêcher plusieurs utilisateurs d'utiliser simultanément le même compte, ce qui, selon moi, devrait être possible.

Vous pouvez donc stocker des jetons, qui contiendront des données chiffrées: IP + chaîne secrète + agent utilisateur + version du navigateur utilisateur + OS utilisateur + toute autre information personnelle: encrypt(IP + "some secret string" + request.user_agent + ...). Et puis, vous pouvez définir une session ou un cookie avec ce jeton. Et à chaque demande, vous pouvez le récupérer: si l'utilisateur est le même? Utilise-t-il le même navigateur et la même version de navigateur du même système d'exploitation, etc.

Vous pouvez également utiliser des jetons dynamiques: vous changez de jeton à chaque demande, donc un seul utilisateur peut utiliser le système par session, car chaque jeton de demande sera modifié, un autre utilisateur sera déconnecté tant que son jeton sera expiré.

10
fl00r

Ce bijou fonctionne bien: https://github.com/devise-security/devise-security

Ajouter au Gemfile

gem 'devise-security'

après l'installation du bundle

Rails generate devise_security:install

Exécutez ensuite

Rails g migration AddSessionLimitableToUsers unique_session_id

Modifier le fichier de migration

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

Exécutez ensuite

rake db:migrate

Modifiez votre fichier app/models/user.rb

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

Terminé. La connexion à partir d'un autre navigateur supprimera désormais toutes les sessions précédentes. La gemme réelle informe l'utilisateur qu'il est sur le point de tuer une session en cours avant de se connecter.

30
scarver2

C'est ainsi que j'ai résolu le problème de session en double.

routes.rb

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

contrôleur 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

Modèle utilisateur Ajouter un champ de chaîne via une migration nommée login_token

Cela remplace le contrôleur Devise Session par défaut mais en hérite également. Lors d'une nouvelle session, un jeton de session de connexion est créé et stocké dans login_token sur le modèle Utilisateur. Dans le contrôleur d'application, nous appelons check_concurrent_session qui se déconnecte et redirige le current_user après avoir appelé le duplicate_session? fonction.

Ce n'est pas la façon la plus propre de s'y prendre, mais cela fonctionne certainement.

3
nulltek

Pour ce qui est de l'implémenter dans Devise, ajoutez-le à votre modèle User.rb. Quelque chose comme ça les déconnectera automatiquement (non testé).

  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 
2
Dex

J'ai trouvé que la solution dans la publication d'origine ne fonctionnait pas tout à fait pour moi. Je voulais que le premier utilisateur soit déconnecté et qu'une page de connexion soit présentée. De plus, la méthode sign_out_and_redirect(current_user) ne semble pas fonctionner comme je m'y attendais. En utilisant le remplacement de SessionsController dans cette solution, je l'ai modifié pour utiliser les websockets comme suit:

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

Assurez-vous que toutes les pages Web sont abonnées au canal de déconnexion et associez-le au même logout_subscribe_address action. Dans mon application, chaque page dispose également d'un bouton de "déconnexion" qui déconnecte le client via l'action Destroy de la session de conception. Lorsque la réponse du Websocket est déclenchée dans la page Web, il clique simplement sur ce bouton - la logique de déconnexion est invoquée et le premier utilisateur se voit présenter la page de connexion.

Cette solution ne nécessite pas non plus le skip_before_filter :check_concurrent_session et le modèle login_token car il déclenche la déconnexion forcée sans préjudice.

Pour mémoire, le devise_security_extension semble également fournir la fonctionnalité pour le faire. Il affiche également une alerte appropriée avertissant le premier utilisateur de ce qui s'est passé (je n'ai pas encore compris comment le faire).

1
davidm

Gardez une trace des IP uniq utilisées par utilisateur. De temps en temps, effectuez une analyse sur ces adresses IP - le partage serait évident si un seul compte a des connexions simultanées de différents FAI dans différents pays. Notez que le simple fait d'avoir une IP différente n'est pas un motif suffisant pour la considérer comme partagée - certains FAI utilisent des proxys à tour de rôle, donc chaque hit serait nécessairement une IP différente.

0
Marc B

Bien que vous ne puissiez pas empêcher de manière fiable les utilisateurs de partager un compte, ce que vous pouvez faire (je pense) est d'empêcher plus d'un utilisateur d'être connecté en même temps au même compte. Je ne sais pas si cela est suffisant pour votre modèle d'entreprise, mais cela résout beaucoup de problèmes discutés dans les autres réponses. J'ai implémenté quelque chose qui est actuellement en version bêta et semble fonctionner assez bien - il y a quelques notes ici

0
chrispanda