Wie erzwinge ich eine Benutzerabmeldung in Django?

76

In meiner Django-App möchte ich unter bestimmten Bedingungen Benutzer dazu zwingen können, sich mit einem Benutzernamen abzumelden. Nicht unbedingt der aktuelle Benutzer, der angemeldet ist, sondern ein anderer Benutzer. Daher enthält die Anforderungsmethode in meiner Ansicht keine Sitzungsinformationen zu dem Benutzer, den ich abmelden möchte.

Ich bin mit django.auth und mit auth vertraut. Abmeldemethode, aber es nimmt Anfrage als Argument. Gibt es einen "Django-Weg", um den Benutzer abzumelden, wenn ich nur den Benutzernamen habe? Oder muss ich mein eigenes Abmelde-SQL rollen?

Sergey Golovchenko
quelle
Warum möchten Sie einen anderen Benutzer als den Benutzer abmelden, der sich angemeldet hat? Wenn Sie Sitzungen mit Browserlänge verwenden, sind die Benutzer, die nicht angemeldet sind, bereits abgemeldet.
Rama Vadakattu
3
Angenommen, ich muss den Benutzer abmelden, wenn das Kennwort geändert wurde. Ich habe über 100.000 Benutzer und ungefähr 140.000 Sitzungen in der Sitzungstabelle. Wie geht man damit effizient um?
Kjagiello
1
@kjagiello Schauen Sie sich das Backend von github.com/QueraTeam/django-qsessions an . Mit ihm können Sie einen Benutzer einfach abmelden : user.session_set.all().delete(). Haftungsausschluss: Ich bin der Autor von Django-Qsessions.
Mohammad Javad Naderi

Antworten:

82

Ich glaube, es gibt noch keinen sanktionierten Weg, dies in Django zu tun.

Die Benutzer-ID wird im Sitzungsobjekt gespeichert, jedoch codiert. Leider bedeutet dies, dass Sie alle Sitzungen durchlaufen, dekodieren und vergleichen müssen ...

Zwei schritte:

Löschen Sie zuerst die Sitzungsobjekte für Ihren Zielbenutzer. Wenn sie sich von mehreren Computern aus anmelden, verfügen sie über mehrere Sitzungsobjekte.

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]

Dann, wenn Sie müssen, sperren Sie sie aus ....

user.is_active = False
user.save()
Harold
quelle
1
Vielen Dank für den Vorschlag, diese Naht eher Brute-Force-Lösung, die ich zu vermeiden versuchte. Wenn es jedoch keine anderen Optionen gibt, muss ich mich möglicherweise dafür entscheiden, wahrscheinlich mit einer kleinen Verbesserung, anstatt "alle" Sitzungen zu erhalten, die innerhalb der letzten "x" Minuten aktualisiert wurden, was hoffentlich die Leistung drastisch verbessern würde.
Sergey Golovchenko
Kein Problem. Das Filtern der Sitzungsdaten nach der letzten Aktualisierung wäre eine lohnende Verbesserung.
Harold
4
Es ist erwähnenswert, dass Django 1.7 die Ungültigmachung von Sitzungen bei Kennwortänderungen unterstützt .
DavidM
7
Für django 1.8.2 muss die letzte Zeichenfolge geändert werden (konvertieren Sie beide Elemente in str), wie: [s.delete () für s in Session.objects.all (), wenn str (s.get_decoded (). Get ('_ auth_user_id ')) == str (user.id)]
Dmitriy Yusupov
59

Obwohl Harolds Antwort in diesem speziellen Fall funktioniert, kann ich mindestens zwei wichtige Probleme damit erkennen:

  1. Diese Lösung kann nur mit einer Datenbanksitzungs- Engine verwendet werden . In anderen Situationen (Cache, Datei, Cookie) wird das SessionModell nicht verwendet.
  2. Wenn die Anzahl der Sitzungen und Benutzer in der Datenbank zunimmt, wird dies ziemlich ineffizient.

Um diese Probleme zu lösen, schlage ich vor, dass Sie das Problem anders angehen. Die Idee ist, irgendwo das Datum zu speichern, an dem der Benutzer für eine bestimmte Sitzung angemeldet war, und das letzte Mal, als Sie einen Benutzer zum Abmelden aufgefordert haben.

Dann , wenn jemand Zugriff auf Ihrer Website, wenn das Datum angemeldet ist niedriger als das Abmeldedatum, können Sie zwangsAbmelde den Benutzer. Wie Dan sagte, gibt es keinen praktischen Unterschied zwischen dem sofortigen Abmelden eines Benutzers oder seiner nächsten Anfrage an Ihre Site.

Lassen Sie uns nun eine mögliche Implementierung dieser Lösung für django 1.3b1 sehen . In drei Schritten:

1. Speichern Sie in der Sitzung das letzte Anmeldedatum

Glücklicherweise gibt das Django-Authentifizierungssystem ein Signal aus, das aufgerufen wird user_logged_in. Sie müssen nur diese Signale registrieren und das aktuelle Datum in der Sitzung speichern. Am Ende Ihres 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. Fordern Sie eine Force-Abmeldung für einen Benutzer an

Wir müssen dem UserModell nur ein Feld und eine Methode hinzufügen . Es gibt mehrere Möglichkeiten, dies zu erreichen ( Benutzerprofile , Modellvererbung usw.), die jeweils Vor- und Nachteile haben.

Der Einfachheit halber werde ich hier die Modellvererbung verwenden. Wenn Sie sich für diese Lösung entscheiden, vergessen Sie nicht , ein benutzerdefiniertes Authentifizierungs-Backend zu schreiben .

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()

Wenn Sie dann die Abmeldung für den Benutzer erzwingen möchten johndoe, müssen Sie nur:

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

3. Implementieren Sie die Zugriffsprüfung

Der beste Weg hier ist, eine Middleware zu verwenden, wie Dan vorgeschlagen hat. Diese Middleware zugreifen request.user, also setzen Sie sie brauchen , um nach 'django.contrib.auth.middleware.AuthenticationMiddleware' Ihrer MIDDLEWARE_CLASSESEinstellung.

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)

Das sollte es tun.


Anmerkungen

  • Beachten Sie die Auswirkungen auf die Leistung, wenn Sie ein zusätzliches Feld für Ihre Benutzer speichern. Durch die Verwendung der Modellvererbung wird ein zusätzliches Element hinzugefügt JOIN. Durch die Verwendung von Benutzerprofilen wird eine zusätzliche Abfrage hinzugefügt. Direktes Ändern Userist der beste Weg, um die Leistung zu verbessern, aber es ist immer noch ein haariges Thema .
  • Wenn Sie diese Lösung auf einer vorhandenen Site bereitstellen, haben Sie wahrscheinlich Probleme mit vorhandenen Sitzungen, die nicht über den 'LAST_LOGIN_DATE'Schlüssel verfügen . Sie können den Middleware-Code ein wenig anpassen, um diesen Fall zu behandeln:

    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)
    
  • In Django 1.2.x gibt es kein user_logged_inSignal. Zurückgreifen auf das Überschreiben der loginFunktion:

    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()
    
Clément
quelle
Ich bin ein Neuling, also entschuldigen Sie mich bitte, wenn ich falsch verstehe, aber sollte es nicht sein: update_session_last_login (Absender, Benutzer = 'Benutzer', Anfrage = 'Anfrage', ** kwargs):?
Rix
Eigentlich denke ich, dass Clément gemeint hat: def update_session_last_login (Absender, Benutzer, Anfrage, ** kwargs):.
Dylan
46

Ich brauchte etwas Ähnliches in meiner App. In meinem Fall, wenn ein Benutzer auf inaktiv gesetzt war, wollte ich sicherstellen, dass der Benutzer abgemeldet ist und die Site nicht weiter nutzen kann, wenn er bereits angemeldet war. Nachdem ich diesen Beitrag gelesen hatte, kam ich zu folgender Lösung:

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)

Fügen Sie diese Middleware einfach in Ihre Einstellungen ein und los geht's. Wenn Sie Kennwörter ändern, können Sie ein neues Feld in das Benutzerprofilmodell einfügen, das einen Benutzer zum Abmelden zwingt, den Wert des Felds anstelle von is_active oben überprüft und das Feld auch deaktiviert, wenn sich ein Benutzer anmeldet. Letzteres kann mit Djangos user_logged_in Signal gemacht werden.

Tony Abou-Assaleh
quelle
1
Verwenden Sie das angegebene Feld für das geänderte Passwort. Wenn Sie an zwei Orten angemeldet sind und Ihr Passwort bei A ändern, melden Sie sich bei A an. Das Flag für das geänderte Passwort wird deaktiviert und Sie bleiben bei B angemeldet, ohne das neue Passwort eingeben zu müssen.
Mark
1
@ Mark, du bist absolut richtig. Ich habe dieses Problem bei der Verwendung von WebSockets festgestellt. Die Lösung bestand darin, die Verbindungen selbst zu speichern, damit ich sie manipulieren konnte. Klingt so, als wäre hier ein ähnlicher Ansatz erforderlich, dh eine andere Sitzungstabelle, die Benutzer Sitzungs-IDs zuordnet.
Tony Abou-Assaleh
5
Ihre Lösung war genau das, was ich mir vorgestellt hatte, bevor ich nach anderen Wegen gesucht habe, und ich habe es schließlich so gemacht, nachdem ich auf dieser Seite gelandet bin. Nur ein Kommentar: Ihr Zustand könnte so geschrieben werden, um (meiner Meinung nach) klarer zu sein:if request.user.is_authenticated() and not request.user.is_active
Yoone
7

Vielleicht ein bisschen Middleware, die auf eine Liste von Benutzern verweist, die gezwungen waren, sich abzumelden. Wenn der Benutzer das nächste Mal versucht, etwas zu tun, melden Sie sie dann ab, leiten Sie sie um usw.

Es sei denn natürlich, sie müssen sofort abgemeldet werden. Andererseits würden sie es erst bemerken, wenn sie das nächste Mal versuchten, eine Anfrage zu stellen, sodass die oben genannte Lösung möglicherweise einfach funktioniert.

Dan
quelle
6

Dies ist eine Antwort auf Balons Anfrage:

Ja, mit ungefähr 140.000 Sitzungen kann ich sehen, warum Harolds Antwort möglicherweise nicht so schnell ist, wie Sie möchten!

Ich würde empfehlen, ein Modell hinzuzufügen, dessen einzige zwei Eigenschaften Fremdschlüssel Userund SessionObjekte sind. Fügen Sie dann eine Middleware hinzu, die dieses Modell mit den aktuellen Benutzersitzungen auf dem neuesten Stand hält. Ich habe diese Art von Setup schon einmal verwendet. In meinem Fall habe ich das sessionprofileModul von diesem Single Sign-On-System für phpBB ausgeliehen (siehe Quellcode im Ordner "django / sessionprofile") und dies (glaube ich) würde Ihren Anforderungen entsprechen.

Am Ende würden Sie irgendwo in Ihrem Code eine Verwaltungsfunktion wie diese haben (unter der Annahme der gleichen Codenamen und des gleichen Layouts wie im sessionprofileoben verlinkten Modul):

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]

(Ich denke, dies wird auch die SessionProfileObjekte löschen. Soweit ich mich erinnere, besteht Djangos Standardverhalten beim ForeignKeyLöschen eines Objekts, auf das von a verwiesen wird, darin, es zu kaskadieren und auch das Objekt zu löschen, das das enthält. ForeignKeyWenn nicht, ist es trivial genug, das zu löschen Inhalt, sessionProfileswenn Sie fertig sind.)

pythonian4000
quelle
2

Als Tony Abou-Assaleh musste ich auch Benutzer abmelden, die auf inaktiv eingestellt waren, und begann mit der Implementierung seiner Lösung. Nach einiger Zeit stellte ich fest, dass die Middleware bei allen Anforderungen eine DB-Abfrage erzwingt (um zu überprüfen, ob der Benutzer blockiert wurde) und somit die Leistung auf Seiten beeinträchtigt, für die keine Anmeldung erforderlich ist.

Ich habe ein benutzerdefiniertes Benutzerobjekt und Django> = 1.7. Am Ende habe ich also seine get_session_auth_hashFunktion überschrieben , um die Sitzung ungültig zu machen, wenn der Benutzer inaktiv ist. Eine mögliche Implementierung ist:

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

Damit dies funktioniert, django.contrib.auth.middleware.SessionAuthenticationMiddlewaresollte in seinsettings.MIDDLEWARE_CLASSES

Tzach
quelle
2

Sie können dazu auch die direkte Django-Funktion verwenden. Sie aktualisiert und protokolliert alle anderen Sitzungen für den Benutzer mit Ausnahme der aktuellen.

from django.contrib.auth import update_session_auth_hash
update_session_auth_hash(self.request, user)

Dokumente für update_session_auth_hash hier .

Pankaj Sharma
quelle
0

Wie bereits erwähnt, können Sie alle Sitzungen in der Datenbank durchlaufen, alle dekodieren und die Sitzungen dieses Benutzers löschen. Aber es ist langsam, besonders wenn Ihre Site viel Verkehr hat und es viele Sitzungen gibt.

Wenn Sie eine schnellere Lösung benötigen, können Sie ein Sitzungs-Backend verwenden, mit dem Sie die Sitzungen eines bestimmten Benutzers abfragen und abrufen können. In diesen Sitzungs-Backends verfügt die Sitzung über einen Fremdschlüssel für den Benutzer, sodass Sie nicht alle Sitzungsobjekte durchlaufen müssen:

Mit diesen Backends können Sie alle Sitzungen eines Benutzers in einer einzigen Codezeile löschen:

user.session_set.all().delete()

Haftungsausschluss: Ich bin der Autor von django-qsessions.

Mohammad Javad Naderi
quelle
-1

aus django.contrib.sessions.models Sitzung importieren

Benutzersitzung löschen

[s.delete() for s in Session.objects.all() if s.get_decoded().get('_auth_user_hash') == user.get_session_auth_hash()]
Ashish Kumar Verma
quelle
-4

Sogar ich habe mich diesem Problem gestellt. Nur wenige Spammer aus Indien veröffentlichen immer wieder Informationen über diese Baba und Molvi für Liebeslösungen.

Was ich getan habe, war zum Zeitpunkt der Veröffentlichung gerade diesen Code eingefügt:

if request.user.is_active==False:
            return HttpResponse('You are banned on the site for spaming.')
Pulkit Sharma
quelle
Diese Antwort beantwortet die Frage nicht. Er bittet nicht um ein Mittel, um einem gelöschten Benutzer die Antwort zu verweigern
Mohammed Shareef C