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

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

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

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

【问题讨论】:

    标签: django authentication 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 问题,这似乎是一个需要解决的合乎逻辑的问题。删除外键似乎会使您原本优雅的解决方案无效。
      猜你喜欢
      • 1970-01-01
      • 2019-02-13
      • 1970-01-01
      • 2016-11-30
      • 1970-01-01
      • 2018-09-03
      • 1970-01-01
      • 1970-01-01
      • 2019-11-13
      相关资源
      最近更新 更多