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

Ser*_*nko 70 django django-authentication

在我的django应用程序中,在某些条件下,我需要能够通过用户名强制用户注销.不一定是当前登录的用户,而是其他一些用户.因此,我视图中的请求方法没有关于我要注销的用户的任何会话信息.

我熟悉django.auth,并使用auth.logout方法,但它将请求作为参数.如果我拥有的是用户名,是否有"django-way"来记录用户?或者我必须滚动自己的注销sql?

Har*_*old 75

我不认为在Django中有一个受制裁的方法.

用户标识存储在会话对象中,但它是经过编码的.不幸的是,这意味着你必须遍历所有会话,解码和比较......

两个步骤:

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

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]
Run Code Online (Sandbox Code Playgroud)

然后,如果你需要,把它们锁起来....

user.is_active = False
user.save()
Run Code Online (Sandbox Code Playgroud)

  • 对于django 1.8.2,最后一个字符串需要进行一些更改(将两个元素都转换为str),如下:[s.delete()用于Session.objects.all()中的s,如果是str(s.get_decoded().get('_ auth_user_id) '))== str(user.id)] (6认同)
  • 值得注意的是Django 1.7支持[密码更改时会话失效](https://docs.djangoproject.com/en/1.7/topics/auth/default/#session-invalidation-on-password-change). (3认同)

Clé*_*ent 55

虽然哈罗德的答案适用于这个具体案例,但我至少可以看到两个重要问题:

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

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

然后,只要有人访问您的站点,如果登录日期低于注销日期,您可以强制注销该用户.正如丹所说,立即注销用户或下次请求访问您的网站没有实际区别.

现在,让我们看看这个解决方案的可能实现,对于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)
Run Code Online (Sandbox Code Playgroud)

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()
Run Code Online (Sandbox Code Playgroud)

然后,如果要强制注销用户johndoe,您只需:

from myapp.models import MyUser
MyUser.objects.get(username='johndoe').force_logout()
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

应该这样做.


笔记

  • 请注意为用户存储额外字段的性能影响.使用模型继承将添加额外的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)
    
    Run Code Online (Sandbox Code Playgroud)
  • 在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()
    
    Run Code Online (Sandbox Code Playgroud)


Ton*_*leh 44

我的应用程序中需要类似的东西.在我的情况下,如果用户设置为非活动状态,我想确保用户是否已登录,他们将被注销并且无法继续使用该网站.看完这篇文章后,我找到了以下解决方案:

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)
Run Code Online (Sandbox Code Playgroud)

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

  • 这真是太棒了!很简单!谢谢 (6认同)
  • 在寻找其他方法之前,您的解决方案正是我的想法,在登陆此页面后我终于做到了这一点.只有一条评论:你的情况可以这样写(在我看来)更清楚:`if request.user.is_authenticated()而不是request.user.is_active` (5认同)
  • @Mark,你是完全正确的。我在使用 WebSocket 时遇到了这个问题。我采用的解决方案是自行管理存储连接,以便我可以操纵它们。听起来这里需要类似的方法,即将用户映射到会话 ID 的另一个会话表。 (2认同)

小智 7

也许,一些中间件引用了被强制注销的用户列表.下次用户尝试执行任何操作时,请将其注销,然后重定向,等等.

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


小智 5

这是对Balon的查询的回应:

是的,有大约140k会话迭代我可以看出为什么哈罗德的答案可能没有你想要的那么快!

我建议的方法是添加一个只有两个属性是外键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]
Run Code Online (Sandbox Code Playgroud)

(我认为这也将删除SessionProfile对象,因为我记得,当ForeignKey删除a引用的对象时,Django的默认行为是级联它并且还删除包含该对象的对象ForeignKey,但如果不是,那么删除它就足够了完成后的内容sessionProfiles.)