82

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

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

4

10 回答 10

89

Update:

Since Django 1.7, users are automatically logged-out when their password changes. On each request, the current password hash is compared to the value saved in their session and if doesn't match, the user is logged-out.

So, a simple password update has the effect of logging the user out. You can then disable the account for login, or advise them to use the password reset feature to set a new password and log in again.

Original:

I don't think there is a sanctioned way to do this in Django yet.

The user id is stored in the session object, but it is encoded. Unfortunately, that means you'll have to iterate through all sessions, decode and compare...

Two steps:

First delete the session objects for your target user. If they log in from multiple computers they will have multiple session objects.

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]

Then, if you need to, lock them out....

user.is_active = False
user.save()
于 2009-06-05T04:54:13.017 回答
66

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

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

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

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

现在,让我们看看这个解决方案的可能实现,用于django 1.3b1。分三步:

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

幸运的是,Django 身份验证系统公开了一个名为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()

然后,如果你想强制注销用户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()
    
于 2011-02-20T07:16:06.290 回答
51

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

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信号来完成。

于 2011-10-24T06:07:25.377 回答
7

也许,一些中间件引用了被迫注销的用户列表。下次用户尝试做任何事情时,将它们注销,重定向它们等。

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

于 2009-06-09T23:24:30.393 回答
6

这是对 Balon 询问的回应:

是的,大约有 140k 会话要迭代,我可以理解为什么 Harold 的回答可能没有你想的那么快!

我推荐的方法是添加一个只有两个属性是外键UserSession对象的模型。然后添加一些中间件,使该模型与当前用户会话保持同步。我以前使用过这种设置;就我而言,我sessionprofile从这个单点登录系统中借用了 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对象,据我回忆,当删除 a 引用的对象时,Django 的默认行为ForeignKey是级联它并删除包含 的对象ForeignKey,但如果不是,那么删除完成时的内容sessionProfiles。)

于 2011-02-18T08:33:21.287 回答
3

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

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

update_session_auth_hash 的文档在这里

于 2019-02-28T05:08:27.753 回答
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

于 2015-08-31T15:25:40.590 回答
1

正如其他人所说,您可以遍历数据库中的所有会话,解码所有会话,并删除属于该用户的会话。但它很慢,特别是如果您的网站流量很大并且有很多会话。

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

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

user.session_set.all().delete()

免责声明:我是django-qsessions.

于 2018-04-11T12:57:34.220 回答
0

从 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()]
于 2016-06-03T09:29:05.720 回答
-5

即使我遇到了这个问题。很少有来自印度的垃圾邮件发送者不断发布关于那些 Baba 和 Molvi 的爱情解决方案。

我所做的是在发布时刚刚插入了这段代码:

if request.user.is_active==False:
            return HttpResponse('You are banned on the site for spaming.')
于 2017-08-27T12:06:29.297 回答