如何在Django中强制用户注销?


77

在某些情况下,在我的Django应用中,我希望能够强制用户通过用户名注销。不一定是当前登录的用户,而是另一个用户。因此,在我看来,request方法没有任何有关我要注销的用户的会话信息。

我熟悉django.auth和auth。注销方法,但是它将请求作为参数。如果我只有用户名,是否存在“ Django方式”将用户注销?还是我必须推出自己的注销SQL?


为什么要注销除登录用户以外的其他用户?如果您正在使用浏览器长度的会话,则那些尚未登录的用户已经注销。
Rama Vadakattu,2009年

3
假设更改密码后,我需要注销用户。在会话表中,我有超过10万名用户和约14万个会话。如何有效处理?
kjagiello 2011年

1
@kjagiello看一下github.com/QueraTeam/django-qsessions后端。使用它,您可以轻松注销用户:user.session_set.all().delete()。免责声明:我是django-qsessions的作者。
Mohammad Javad Naderi

Answers:


83

我认为Django中尚无批准的方法可以做到这一点。

用户ID存储在会话对象中,但已被编码。不幸的是,这意味着您必须遍历所有会话,进行解码和比较...

两步:

首先删除目标用户的会话对象。如果他们从多台计算机登录,则将有多个会话对象。

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]

然后,如果需要,将其锁定...。

user.is_active = False
user.save()

1
谢谢您的建议,这是我试图避免的那种蛮横的解决方案。但是,如果没有其他选择,我可能不得不这样做,可能会有一点改进,而不是让“所有”会话都获得在最近的“ x”分钟内更新的会话,希望这将大大提高性能。
谢尔盖·戈洛维琴科

没问题。筛选上一次更新的会话数据将是值得改进的。
哈罗德

4
值得一提的是Django 1.7在密码更改时支持会话无效
DavidM 2014年

7
对于Django 1.8.2,最后一个字符串需要做一些更改(将两个元素都转换为str),例如:[如果str(s.get_decoded()。get('_ auth_user_id '))== str(user.id)]
Dmitriy Yusupov

60

尽管Harold的答案在此特定情况下有效,但我至少可以看到两个重要问题:

  1. 此解决方案只能与数据库会话引擎一起使用。在其他情况下(缓存,文件,cookie),Session将不会使用该模型。
  2. 当数据库中的会话和用户数量增加时,这将变得非常低效。

为了解决这些问题,我建议您采用另一种方法解决该问题。想法是将用户登录给定会话的日期和上次请求用户注销的时间存储在某处。

然后,每当有人访问您的网站时,如果登录日期小于注销日期,则可以强制注销用户。正如dan所说,立即注销用户或在他的下一个请求访问您的站点之间没有实际区别。

现在,让我们看看针对django 1.3b1的该解决方案的可能实现。分三步:

1.在会话中存储上次登录日期

幸运的是,Django auth系统公开了一个名为的信号user_logged_in。您只需要注册该信号,然后将当前日期保存在会话中即可。在您的底部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.要求用户强制注销

我们只需要向模型添加字段和方法User。有多种实现方法(用户配置文件模型继承等),各有优缺点。

为了简单起见,我将在这里使用模型继承,如果您使用此解决方案,请不要忘记编写自定义身份验证后端

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

然后,如果您要强制注销user johndoe,则只需执行以下操作:

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

3.实施访问检查

最好的方法是按照dan的建议使用中间件。该中间件将访问request.user,所以你需要把它后, 'django.contrib.auth.middleware.AuthenticationMiddleware'你的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)

那应该做。


笔记

  • 请注意为用户存储额外字段的性能含义。使用模型继承会增加额外的负担JOIN。使用用户个人资料将添加一个额外的查询。直接进行修改User是明智的最佳方法,但这仍然是一个多毛的话题
  • 如果在现有站点上部署该解决方案,则现有会话可能会遇到一些麻烦,因为这些会话没有'LAST_LOGIN_DATE'密钥。您可以调整一下中间件代码来处理这种情况:

    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)
    
  • 在django 1.2.x中,没有user_logged_in信号。回退到覆盖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()
    

我是新手,所以如果我误会了,请原谅,但不应该是:update_session_last_login(sender,user ='user',request ='request',** kwargs):?
rix

实际上,我认为Clément的意思是:def update_session_last_login(sender,user,request,** kwargs):。
Dylan 2014年

46

我的应用程序中需要类似的内容。就我而言,如果将用户设置为非活动状态,我想确保该用户是否已经登录,这样他们将被注销并且无法继续使用该站点。阅读这篇文章后,我得出以下解决方案:

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)

只需在您的设置中添加此中间件,即可使用。在更改密码的情况下,您可以在userprofile模型中引入一个新字段,以强制用户注销,检查该字段的值而不是上面的is_active,并且还可以在用户登录时取消设置该字段。后者可以用Django的user_logged_in信号完成。


1
使用提到的更改密码字段。如果您在2个位置登录,并且在A处更改了密码,则在A处登录。更改后的密码标志将被取消设置,并且您将保持登录状态,而无需输入新密码。
2012年

1
@Mark,您绝对正确。使用WebSocket时遇到了这个问题。我采用的解决方案是自己管理存储的连接,以便我可以操纵它们。听起来这里需要类似的方法,即,另一个将用户映射到会话ID的会话表。
Tony Abou-Assaleh 2012年

5
在寻求其他方法之前,您的解决方案正是我的初衷,到达此页面后,我终于做到了这一点。仅有一条评论:您的情况可以这样写(在我看来)更加明确:if request.user.is_authenticated() and not request.user.is_active
Yoone

7

也许,一些中间件引用了被迫注销的用户列表。下次用户尝试执行任何操作时,先注销然后重定向,依此类推。

当然,除非他们需要立即注销。但是话又说回来,他们直到下次尝试发出请求时才注意到,因此上述解决方案可能会起作用。


6

这是对Balon的查询的回应:

是的,大约有140k的会话要遍历,我可以理解为什么Harold的答案可能没有您想的那么快!

我建议的方法是添加一个模型,该模型的唯一两个属性是UserSession对象的外键。然后添加一些中间件,以使该模型与当前用户会话保持最新。我以前使用过这种设置;就我而言,我sessionprofile从此Single Sign-On系统中借用了phpBB的模块(请参阅“ django / sessionprofile”文件夹中的源代码),并且此(我认为)可以满足您的需求。

您最终将在代码中的某处有一些管理功能,如下所示(假定代码名称和布局sessionprofile与上面链接的模块中的代码相同):

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]

(我认为这也会删除SessionProfile对象,就像我记得的那样,当ForeignKey删除由a引用的对象时,Django的默认行为是级联它并删除包含的对象ForeignKey,但如果不删除,则删除该对象很简单。sessionProfiles完成时的内容。)


2

作为Tony Abou-Assaleh,我还需要注销设置为非活动状态的用户,因此我首先实现了他的解决方案。一段时间后,我发现中间件正在对所有请求强制执行数据库查询(以检查用户是否被阻止),从而损害了不需要登录的页面的性能。

我有一个自定义用户对象,而Django> = 1.7,因此我最终要做的是重写其get_session_auth_hash功能,以在用户不活动时使会话无效。可能的实现是:

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

为了这个工作,django.contrib.auth.middleware.SessionAuthenticationMiddleware应该在settings.MIDDLEWARE_CLASSES


2

您还可以使用django直接功能来执行此操作,它将更新并注销该用户的所有其他会话(当前会话除外)。

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

这里的update_session_auth_hash文档。


0

正如其他人所述,您可以遍历数据库中的所有会话,对所有会话进行解码,然后删除属于该用户的会话。但这很慢,尤其是在您的网站流量很高且会话很多的情况下。

如果您需要更快的解决方案,则可以使用会话后端,该后端可让您查询并获取特定用户的会话。在这些会话后端中,会话具有用户的外键,因此您不需要遍历所有会话对象:

使用这些后端,可以在一行代码中删除用户的所有会话:

user.session_set.all().delete()

免责声明:我是的作者django-qsessions


-1

从django.contrib.sessions.models导入会话

删除用户会话

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

-4

甚至我也面临这个问题。来自印度的垃圾邮件发送者很少继续发布有关Baba和Molvi的爱情解决方案。

我所做的是在发布时刚刚插入以下代码:

if request.user.is_active==False:
            return HttpResponse('You are banned on the site for spaming.')

这个答案不能回答问题。他没有要求拒绝回应被删除用户的方法
Mohammed Shareef C
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.