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

发布于 2024-07-23 09:58:52 字数 229 浏览 6 评论 0原文

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

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

In my Django app under certain conditions I want to be able to force users to log out by a username. Not necessarily the current user who is logged in, but another user. So, the request method in my view doesn't have any session information about the user that I want to logout.

I am familiar with django.auth and with auth. logout method, but it takes request as an argument. Is there a "Django-way" to log the user out if all I have is the username? Or do I have to roll my own logout SQL?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

静若繁花 2024-07-30 09:58:52

更新:

从 Django 1.7 开始,用户在密码更改时会自动注销。 对于每个请求,会将当前密码哈希值与会话中保存的值进行比较,如果不匹配,则用户将被注销。

因此,简单的密码更新就会导致用户注销。 然后,您可以禁用该帐户登录,或建议他们使用密码重置功能设置新密码并重新登录。

原文:

我认为 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()

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()
无法回应 2024-07-30 09:58:52

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

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

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

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

3. 实施访问检查

最好的方法是使用 中间件 正如丹建议的那样。 此中间件将访问 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 
    
       类 ForceLogoutMiddleware(对象): 
           def process_request(自身,请求): 
               如果 request.user.is_authenticated() 和 request.user.force_logout_date 和 \ 
                  (“LAST_LOGIN_DATE”不在 request.session 或 \ 
                    request.session['LAST_LOGIN_DATE'] <   request.user.force_logout_date ): 
                   注销(请求) 
      
  • 在 django 1.2.x 中,没有 user_logged_in 信号。 回退到覆盖 login 函数:

     from django.contrib.auth 将登录名导入为 dj_login 
       从日期时间导入日期时间 
    
       def 登录(请求,用户): 
           dj_login(请求,用户) 
           request.session['LAST_LOGIN_DATE'] = datetime.now() 
      

Although Harold's answer works in this specific case, I can see at least two important issues with it:

  1. This solution can only be used with a database session engine. In other situations (cache, file, cookie) the Session model would not be used.
  2. When the number of sessions and users in database grows, this becomes quite inefficient.

To solve those issues, I suggest you take another approach at the problem. The idea is to store somewhere the date when the user was logged in for a given session, and the last time you requested a user to be logged out.

Then whenever someone access your site, if the logged in date is lower than the log out date, you can force-logout the user. As dan said, there's no practical difference between logging out a user immediately or on his next request to your site.

Now, let's see a possible implementation of this solution, for django 1.3b1. In three steps:

1. store in the session the last login date

Fortunately, Django auth system exposes a signal called user_logged_in. You just have to register that signals, and save the current date in the session. At the bottom of your 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. request a force logout for a user

We just need to add a field and a method to the User model. There's multiple ways to achieve that (user profiles, model inheritance, etc.) each with pros and cons.

For the sake of simplicity, I'm gonna use model inheritance here, if you go for this solution, don't forget to write a custom authentication backend.

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

Then, if you want to force logout for user johndoe, you just have to:

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

3. implement the check on access

Best way here is to use a middleware as dan suggested. This middleware will access request.user, so you need to put it after 'django.contrib.auth.middleware.AuthenticationMiddleware' in your MIDDLEWARE_CLASSES setting.

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)

That should do it.


Notes

  • Be aware of the performance implication of storing an extra field for your users. Using model inheritance will add an extra JOIN. Using user profiles will add an extra query. Modifying directly the User is the best way performance wise, but it is still a hairy topic.

  • If you deploy that solution on an existing site, you will probably have some trouble with existing sessions, which won't have the 'LAST_LOGIN_DATE' key. You can adapt a bit the middleware code to deal with that case :

     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, there is no user_logged_in signal. Fall back to overriding the login function:

     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()
    
新人笑 2024-07-30 09:58:52

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

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)

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

I needed something similar in my app. In my case, if a user was set to inactive, I wanted to make sure if the user was already logged in that they will be logged out and not able to continue to use the site. After reading this post, I came to the following solution:

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)

Just add this middleware in your settings and off you go. In the case of changing passwords, you could introduce a new field in the userprofile model that forces a user to logout, check for the value of the field instead of is_active above, and also unset the field when a user logs in. The latter can be done with Django's user_logged_in signal.

甜中书 2024-07-30 09:58:52

也许是一个引用被迫注销的用户列表的中间件。 下次用户尝试执行任何操作时,将其注销,重定向它们等。

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

Perhaps, a bit of middleware that references a list of users who have been forced to log out. Next time the user tries to do anything, log them out then, redirects them, etc.

Unless of course, they need to be logged out immediately. But then again, they wouldn't notice until they next tried to make a request anyway, so the above solution may just work.

居里长安 2024-07-30 09:58:52

这是对 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 对象,据我所知,当删除 ForeignKey 引用的对象时,Django 的默认行为是级联它并删除包含 ForeignKey 的对象code>,但如果没有,那么完成后删除 sessionProfiles 的内容就足够了。)

This is in response to Balon's query:

Yes, with around 140k sessions to iterate through I can see why Harold's answer may not be as fast as you may like!

The way I would recommend is to add a model whose only two properties are foreign keys to User and Session objects. Then add some middleware that keeps this model up-to-date with current user sessions. I have used this sort of setup before; in my case, I borrowed the sessionprofile module from this Single Sign-On system for phpBB (see the source code in the "django/sessionprofile" folder) and this (I think) would suit your needs.

What you would end up with is some management function somewhere in your code like this (assuming the same code names and layout as in the sessionprofile module linked above):

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]

(I think this will also delete the SessionProfile objects, as from what I recall, Django's default behaviour when an object referenced by a ForeignKey is deleted is to cascade it and also delete the object containing the ForeignKey, but if not then it is trivial enough to delete the contents of sessionProfiles when you are done.)

偏爱你一生 2024-07-30 09:58:52

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

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

update_session_auth_hash 的文档此处

You can also use direct django function to do that, it will update and logs out all other sessions for the user, except the current one.

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

Docs for update_session_auth_hash here.

倦话 2024-07-30 09:58:52

作为 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

As Tony Abou-Assaleh, I also needed to log out users who were set to inactive, so I started by implementing his solution. After some time I found out that the middleware is forcing a DB query on all requests (to check if the user was blocked), and thus hurts performance on pages that doesn't require login.

I have a custom user object and Django >= 1.7, so what I ended up doing is overriding its get_session_auth_hash function to invalidate the session when the user is inactive. A possible implementation is:

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

For this to work, django.contrib.auth.middleware.SessionAuthenticationMiddleware should be in settings.MIDDLEWARE_CLASSES

变身佩奇 2024-07-30 09:58:52

正如其他人所说,您可以迭代数据库中的所有会话,对所有会话进行解码,并删除属于该用户的会话。 但它很慢,特别是当您的网站流量很高并且有很多会话时。

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

使用这些后端,删除 的所有会话用户可以通过一行代码完成:

user.session_set.all().delete()

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

As others stated, you can iterate over all sessions in DB, decode all of them, and delete those belonging to that user. But it's slow, particularly if your site has high traffic and there are lots of sessions.

If you need a faster solution, you can use a session backend that lets you query and get the sessions of a specific user. In these session backends, Session has a foreign key to User, so you don't need to iterate over all session objects:

Using these backends, deleting all sessions of a user can be done in a single line of code:

user.session_set.all().delete()

Disclaimer: I am the author of django-qsessions.

痴情换悲伤 2024-07-30 09:58:52

from django.contrib.sessions.models import Session

删除用户会话

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

from django.contrib.sessions.models import Session

deleting user session

[s.delete() for s in Session.objects.all() if s.get_decoded().get('_auth_user_hash') == user.get_session_auth_hash()]
心奴独伤 2024-07-30 09:58:52

即使我也遇到过这个问题。 来自印度的垃圾邮件发送者很少会继续发布关于 Baba 和 Molvi 的爱情解决方案。

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

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

Even I faced this issue. Few spammers from India keep posting about those Baba and Molvi for love solutions.

What I did is at the time of posting just inserted this code:

if request.user.is_active==False:
            return HttpResponse('You are banned on the site for spaming.')
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文