如何查找特定用户的 django 会话?

Posted

技术标签:

【中文标题】如何查找特定用户的 django 会话?【英文标题】:How to lookup django session for a particular user? 【发布时间】:2010-09-19 03:42:33 【问题描述】:

我正在编写一个应用程序,我将从 django 和独立应用程序访问数据库。两者都需要进行会话验证,并且两者的会话应该相同。 Django 有一个内置的身份验证/会话验证,这是我正在使用的,现在我需要弄清楚如何为我的独立应用程序重用相同的会话。

我的问题是如何查找特定用户的 session_key?

从表面上看,没有什么可以将 auth_user 和 django_session 联系在一起

【问题讨论】:

【参考方案1】:

这有点棘手,因为并非每个会话都必须与经过身份验证的用户相关联; Django 的会话框架也支持匿名会话,任何访问您网站的人都会有一个会话,无论他们是否登录。

由于会话对象本身是序列化的,这一点变得更加棘手——因为 Django 无法知道您究竟想要存储哪些数据,它只是将会话数据的字典序列化为字符串(使用 Python 的标准“pickle”模块)并将其填充到您的数据库中。

如果您有会话密钥(将由用户的浏览器作为 cookie 值“sessionid”发送),获取数据的最简单方法就是使用该密钥在 Session 表中查询会话,这返回一个会话对象。然后,您可以调用该对象的“get_decoded()”方法来获取会话数据字典。如果你不使用 Django,你可以查看源代码(django/contrib/sessions/models.py)来了解会话数据是如何反序列化的。

但是,如果您有用户 ID,则需要遍历所有 Session 对象,反序列化每个对象并查找具有名为“_auth_user_id”的键的对象,并且该键的值是用户 ID。

【讨论】:

为什么不在中间件中将会话附加到用户,而不是像在内置版本中那样附加到请求?【参考方案2】:

修改django_session 表以添加显式user_id 可以让生活变得更轻松。假设您这样做(或类似的事情),这里有四种方法可以让您根据自己的喜好进行调整:

fork django.contrib.session 代码。我知道,我知道,这是一个可怕的建议。但它只有 500 行,包括所有后端并减去测试。破解非常简单。仅当您要认真地重新安排一些事情时,这才是最佳路线。

如果您不想分叉,您可以尝试连接到Session.post_save 信号并在那里进行操作。

或者你可以 MonkeyPatch contrib.session.models.Session.save()。只需包装现有方法(或创建一个新方法),分解/合成您需要的任何值,将它们存储在新字段中,然后 super(Session, self).save()

另一种方法是在 settings.py 文件中放入 2 个(是的,两个)中间件类——一个在 SessionMiddleware 之前,一个在 SessionMiddleware 之后。这是因为中间件的处理方式。在SessionMiddleware 之后列出的那个将在入站请求中获得一个已经附加了会话的请求。前面列出的可以对响应进行任何处理和/或更改/重新保存会话。

我们使用最后一种技术的变体来为搜索引擎蜘蛛创建伪会话,让它们可以特殊访问通常仅限成员的材料。我们还检测到 REFERER 字段来自相关搜索引擎的入站链接,并为用户提供对该文章的完全访问权限。

更新:

我的答案现在已经很古老了,尽管它仍然大部分是正确的。请参阅下面@Gavin_Ballard 的最新答案(2014 年 9 月 29 日),了解解决此问题的另一种方法。

【讨论】:

如果您在不分叉 django 代码的情况下这样做,可能会破坏测试。如果会话应用程序位于 INSTALLED_APPS 中,则会话表将被创建两次,导致测试中止。如果您提供了自己的 django_session 表,我不确定删除 django 会话应用程序是否安全。 Django 用户会话现在可以为您提供此功能:github.com/Bouke/django-user-sessions @sww314 - 我快速浏览了链接的 GitHub 项目,它看起来是解决问题的一种非常合理的方法。谢谢!【参考方案3】:

彼得·罗威尔,感谢您的回复。这是一个巨大的帮助。这就是我为使其正常工作所做的。只需要更改 djang.contrib.sessions 中的一个文件。

在 django/contrib/sessions/models.py 中,将 user_id 添加到表中(手动添加到 DB 表或删除表并运行 manage.py syncdb)。

class Session(models.Model):

    ...

    user_id = models.IntegerField(_('user_id'), null=True)

    ...

    def save(self, *args, **kwargs):
        user_id = self.get_decoded().get('_auth_user_id')
        if ( user_id != None ):
            self.user_id = user_id

        # Call the "real" save() method.
        super(Session, self).save(*args, **kwargs)

现在在您登录的视图中(如果您使用 django 的基本登录,则必须覆盖它)

# On login, destroy all prev sessions
        # This disallows multiple logins from different browsers
        dbSessions = Session.objects.filter( user_id = request.user.id )
        for index, dbSession in enumerate( dbSessions ):
            if ( dbSession.session_key != request.session.session_key ):
                dbSession.delete()

这对我有用。

【讨论】:

【参考方案4】:

我找到了这段代码sn-p

from django.contrib.sessions.models import Session
from django.contrib.auth.models import User

session_key = '8cae76c505f15432b48c8292a7dd0e54'

session = Session.objects.get(session_key=session_key)
uid = session.get_decoded().get('_auth_user_id')
user = User.objects.get(pk=uid)

print user.username, user.get_full_name(), user.email

这里 http://scottbarnham.com/blog/2008/12/04/get-user-from-session-key-in-django/

尚未验证,但看起来很简单。

【讨论】:

直截了当,但反向操作。从用户 ID 获取密钥(不修改模型)需要遍历每个会话。 赞成,因为谷歌搜索“django反序列化会话”会导致这里。【参考方案5】:

当我想踢出垃圾邮件发送者时遇到了这个问题。似乎将他们的帐户设置为“非活动”是不够的,因为他们仍然可以参加之前的会话。那么 - 如何删除特定用户的会话,或者如何故意使特定用户的会话过期?

答案是使用last_login 字段来跟踪会话被禁用的时间,这会告诉您expire_date 是两周后,这使您可以对会话表执行有用的过滤:

from django.contrib.sessions.models import Session
from django.contrib.auth.models import User
from datetime import datetime
from dateutil.relativedelta import relativedelta

baduser = User.objects.get(username="whoever")     
two_weeks = relativedelta(weeks=2)
two_hours = relativedelta(hours=2)
expiry = baduser.last_login + two_weeks
sessions = Session.objects.filter(
    expire_date__gt=expiry - two_hours,
    expire_date__lt=expiry + two_hours
) 
print sessions.count() # hopefully a manageable number

for s in sessions:
    if s.get_decoded().get('_auth_user_id') == baduser.id:
        print(s)
        s.delete()

【讨论】:

Hacky 但有效,我喜欢它!不幸的是,当会话后端是 redis 而不是 DB 时,我认为这不起作用。【参考方案6】:

这个答案是在原始问题发布五年后发布的,但是在搜索此问题的解决方案时,此 SO 线程是 Google 搜索结果中排名靠前的结果之一(它仍然是 Django 不支持开箱即用的东西)。

对于只关心登录用户会话的用例,我有一个替代解决方案,它使用额外的UserSession 模型将用户映射到他们的会话,如下所示:

from django.conf import settings
from django.db import models
from django.contrib.sessions.models import Session

class UserSession(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL)
    session = models.ForeignKey(Session)  

然后,您可以在用户登录时保存一个新的UserSession 实例:

from django.contrib.auth.signals import user_logged_in

def user_logged_in_handler(sender, request, user, **kwargs):
    UserSession.objects.get_or_create(user = user, session_id = request.session.session_key)

user_logged_in.connect(user_logged_in_handler)

最后,当您想要列出(并可能清除)特定用户的会话时:

from .models import UserSession

def delete_user_sessions(user):
    user_sessions = UserSession.objects.filter(user = user)
    for user_session in user_sessions:
        user_session.session.delete()

这就是它的具体细节,如果你想了解更多细节,我有一个 blog post 覆盖它。

【讨论】:

我遇到的问题是,当user_logged_in 触发时,会话不一定会保存,因此会出现 FK 约束错误。首先在user_logged_in_handler() 内运行request.session.save() 是否安全?或者,我可以将 FK 字段更改为文本,但这意味着我可能需要做一些额外的清理工作。 附带问题:如果我想跟踪用户的登录怎么办?删除会话我会失去它。如何在不删除的情况下使会话无效? @DanH 我还没有看到这个问题;而不是调用request.session.save() 我想我会选择你的第二个选项并避免使用FK。您唯一需要做的额外清理工作是在user_session.session.delete() 之后调用user_session.delete()。 (在此示例中,您必须将 session() 方法添加到您的 UserSession 模型以查找正确的会话。) @alfetopito:您可以简单地将UserSession 模型的FK 关系更改为不删除CASCADE。然后,您将在 UserSession 表中保留登录记录,同时仍然使会话本身无效。 我遇到了与@DanH 相同的 FK 问题,这似乎是一个需要解决的合乎逻辑的问题。删除外键似乎会使您原本优雅的解决方案无效。

以上是关于如何查找特定用户的 django 会话?的主要内容,如果未能解决你的问题,请参考以下文章

python [Django]清除特定用户的所有会话

React,Django:用户更改密码后如何管理会话?

Django框架之session

在数据库的逗号列表中查找特定值

如何由于 Django 中的不活动而使会话过期?

如何将会话变量连接到 Django 中的登录用户?