如何查找特定用户的 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 会话?的主要内容,如果未能解决你的问题,请参考以下文章