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
Tu ne peux pas le faire.
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é.
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.
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.
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
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).
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.
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