web-dev-qa-db-fra.com

Comment forcer la déconnexion de l'utilisateur dans Django?

Dans certaines applications de mon application Django, je dois pouvoir forcer l'utilisateur à se déconnecter avec un nom d'utilisateur. Pas nécessairement l'utilisateur actuel qui est connecté, mais un autre utilisateur. Ainsi, à mon avis, la méthode de requête ne contient aucune information de session concernant l'utilisateur que je souhaite déconnecter.

Je connais Django.auth et la méthode auth.logout, mais cela prend un argument request . Existe-t-il un "moyen Django" pour déconnecter l'utilisateur si tout ce que j'ai est le nom d'utilisateur? Ou dois-je rouler ma propre logout sql?

65

Je ne pense pas qu'il y ait une manière sanctionnée de faire cela à Django. 

L'identifiant de l'utilisateur est stocké dans l'objet de session, mais il est codé. Malheureusement, cela signifie que vous devrez parcourir toutes les sessions, décoder et comparer ...

Deux étapes:

Commencez par supprimer les objets de session pour votre utilisateur cible. S'ils se connectent à partir de plusieurs ordinateurs, ils auront plusieurs objets de session.

from Django.contrib.sessions.models import Session
from Django.contrib.auth.models import User

# grab the user in question 
user = User.objects.get(username='johndoe')

[s.delete() for s in Session.objects.all() if s.get_decoded().get('_auth_user_id') == user.id]

Ensuite, si vous en avez besoin, verrouillez-les ...

user.is_active = False
user.save()
69
Harold

Bien que la réponse de Harold fonctionne dans ce cas particulier, je peux voir au moins deux problèmes importants:

  1. Cette solution ne peut être utilisée qu'avec une base de données moteur de session . Dans d'autres situations (cache, fichier, cookie), le modèle Session ne serait pas utilisé.
  2. Lorsque le nombre de sessions et d'utilisateurs dans la base de données augmente, cela devient assez inefficace.

Pour résoudre ces problèmes, je vous suggère d’adopter une autre approche du problème. L'idée est de stocker quelque part la date à laquelle l'utilisateur était connecté pour une session donnée et la dernière fois que vous avez demandé à ce qu'un utilisateur soit déconnecté.

Ensuite, chaque fois que quelqu'un accède à votre site, si la date de connexion est inférieure à la date de déconnexion , vous pouvez forcer la déconnexion de l'utilisateur. Comme l'a dit Dan, il n'y a pas de différence pratique entre déconnecter un utilisateur immédiatement ou lors de sa prochaine demande sur votre site.

Voyons maintenant une implémentation possible de cette solution, pour Django 1.3b1 . En trois étapes:

1. enregistrer dans la session la dernière date de connexion

Heureusement, Django système _auth expose un signal appelé user_logged_in. Vous devez simplement enregistrer ces signaux et enregistrer la date du jour dans la session. Au bas de votre models.py:

from Django.contrib.auth.signals import user_logged_in
from datetime import datetime

def update_session_last_login(sender, user=user, request=request, **kwargs):
    if request:
        request.session['LAST_LOGIN_DATE'] = datetime.now()
user_logged_in.connect(update_session_last_login)

2. demander une déconnexion forcée pour un utilisateur

Nous avons juste besoin d'ajouter un champ et une méthode au modèle User. Il y a plusieurs façons d'atteindre cet objectif ( profils utilisateur , héritage du modèle , etc.), chacun présentant des avantages et des inconvénients.

Par souci de simplicité, je vais utiliser l'héritage de modèle ici. Si vous optez pour cette solution, n'oubliez pas de écrivez un backend d'authentification personnalisé .

from Django.contrib.auth.models import User
from Django.db import models
from datetime import datetime

class MyUser(User):
    force_logout_date = models.DateTimeField(null=True, blank=True)

    def force_logout(self):
        self.force_logout_date = datetime.now()
        self.save()

Ensuite, si vous voulez forcer la déconnexion de l'utilisateur johndoe, il vous suffit de:

from myapp.models import MyUser
MyUser.objects.get(username='johndoe').force_logout()

3. mettre en œuvre le contrôle d'accès

Le meilleur moyen ici est d’utiliser un middleware comme suggéré par Dan. Ce middleware va accéder à request.user, vous devez donc le mettre après 'Django.contrib.auth.middleware.AuthenticationMiddleware' dans votre paramètre MIDDLEWARE_CLASSES.

from Django.contrib.auth import logout

class ForceLogoutMiddleware(object):
    def process_request(self, request):
        if request.user.is_authenticated() and request.user.force_logout_date and \
           request.session['LAST_LOGIN_DATE'] < request.user.force_logout_date:
            logout(request)

Ça devrait le faire.


Notes

  • Soyez conscient de l’implication de la performance dans le stockage d’un champ supplémentaire pour vos utilisateurs. L'utilisation de l'héritage de modèle ajoutera un JOIN supplémentaire. L'utilisation de profils d'utilisateurs ajoutera une requête supplémentaire. La modification directe de la variable User constitue le meilleur moyen d'améliorer les performances, mais il s'agit toujours d'un sujet poil .
  • Si vous déployez cette solution sur un site existant, vous rencontrerez probablement des problèmes avec les sessions existantes, qui n'auront pas la clé 'LAST_LOGIN_DATE'. Vous pouvez adapter un peu le code du middleware pour traiter ce cas:

    from Django.contrib.auth import logout
    
    class ForceLogoutMiddleware(object):
        def process_request(self, request):
            if request.user.is_authenticated() and request.user.force_logout_date and \
               ( 'LAST_LOGIN_DATE' not in request.session or \
                 request.session['LAST_LOGIN_DATE'] < request.user.force_logout_date ):
                logout(request)
    
  • Dans Django 1.2.x, il n'y a pas de signal user_logged_in. Revenez à la substitution de la fonction login:

    from Django.contrib.auth import login as dj_login
    from datetime import datetime
    
    def login(request, user):
        dj_login(request, user)
        request.session['LAST_LOGIN_DATE'] = datetime.now()
    
55
Clément

J'avais besoin de quelque chose de similaire dans mon application. Dans mon cas, si un utilisateur était défini sur inactif, je voulais m'assurer que si l'utilisateur était déjà connecté, il sera déconnecté et ne pourra plus continuer à utiliser le site. Après avoir lu ce post, je suis venu à la solution suivante:

from Django.contrib.auth import logout

class ActiveUserMiddleware(object):
    def process_request(self, request):
        if not request.user.is_authenticated():
            return
        if not request.user.is_active:
           logout(request)

Ajoutez simplement ce middleware dans vos paramètres et c'est parti. Dans le cas de la modification des mots de passe, vous pouvez introduire un nouveau champ dans le modèle userprofile qui oblige un utilisateur à se déconnecter, à vérifier la valeur du champ au lieu de is_active ci-dessus et également à désactiver le champ lorsqu'un utilisateur se connecte être fait avec le signal user_logged_in de Django.

42
Tony Abou-Assaleh

Peut-être un peu de middleware qui référence une liste d'utilisateurs qui ont été forcés de se déconnecter. La prochaine fois que l'utilisateur essaie de faire quoi que ce soit, déconnectez-les puis redirigez-les, etc.

À moins bien sûr, ils doivent être déconnectés immédiatement. Mais là encore, ils ne remarqueraient rien avant la prochaine tentative de faire une demande, de sorte que la solution ci-dessus pourrait bien fonctionner.

7
dan

C'est en réponse à la requête de Balon:

Oui, avec environ 140 000 sessions à parcourir, je peux comprendre pourquoi la réponse de Harold peut ne pas être aussi rapide que vous le souhaitez!

La méthode que je recommande consiste à ajouter un modèle dont les deux seules propriétés sont les clés étrangères des objets User et Session. Ajoutez ensuite un middleware qui maintient ce modèle à jour avec les sessions utilisateur actuelles. J'ai déjà utilisé ce type de configuration auparavant; dans mon cas, j'ai emprunté le module sessionprofile à ce système Single Sign-On pour phpBB (voir le code source dans le dossier "Django/sessionprofile") et cela (je pense) conviendrait à vos besoins.

Vous obtiendrez une fonction de gestion quelque part dans votre code, telle que celle-ci (en supposant que les mêmes noms de code et la même présentation soient identiques à ceux du module sessionprofile lié ci-dessus):

from sessionprofile.models import SessionProfile
from Django.contrib.auth.models import User

# Find all SessionProfile objects corresponding to a given username
sessionProfiles = SessionProfile.objects.filter(user__username__exact='johndoe')

# Delete all corresponding sessions
[sp.session.delete() for sp in sessionProfiles]

(Je pense que cela supprimera également les objets SessionProfile, car d'après ce que je me souviens, le comportement par défaut de Django lorsqu'un objet référencé par un ForeignKey est supprimé consiste à le cascader et à supprimer également l'objet contenant le ForeignKey. supprimer le contenu de sessionProfiles lorsque vous avez terminé.)

5
pythonian4000

En tant que Tony Abou-Assaleh, je devais également déconnecter les utilisateurs qui étaient configurés pour devenir inactifs, j'ai donc commencé par implémenter sa solution. Après un certain temps, j'ai découvert que le middleware forçait une requête de base de données sur toutes les requêtes (pour vérifier si l'utilisateur était bloqué), ce qui nuisait aux performances des pages ne nécessitant pas de connexion.

J'ai un objet utilisateur personnalisé et Django> = 1,7, donc ce que j'ai fini par faire est de remplacer sa fonction get_session_auth_hash pour invalider la session lorsque l'utilisateur est inactif. Une implémentation possible est:

def get_session_auth_hash(self):
    if not self.is_active:
        return "inactive"
    return super(MyCustomUser, self).get_session_auth_hash()

Pour que cela fonctionne, Django.contrib.auth.middleware.SessionAuthenticationMiddleware doit être dans settings.MIDDLEWARE_CLASSES

2
Tzach

Vous pouvez également utiliser la fonction directe Django pour cela. Elle mettra à jour les journaux de toutes les autres sessions de l'utilisateur, à l'exception de celle en cours.

from Django.contrib.auth import update_session_auth_hash
update_session_auth_hash(self.request, user)
0
Pankaj Sharma

Vous pouvez utiliser Django-qsessions ( https://github.com/QueraTeam/Django-qsessions ).

C'est un programme de session qui étend le programme cached_db de Django. Les sessions ont une clé étrangère en utilisateur et stockent également l'adresse IP et l'agent utilisateur.

En utilisant Django-qsessions, vous pouvez déconnecter un utilisateur facilement et efficacement:

user = User.objects.get(username='abcd')
user.session_set.all().delete()
0