【问题标题】:Allowing only single active session per user in Django app在 Django 应用程序中,每个用户只允许一个活动会话
【发布时间】:2012-02-14 04:29:36
【问题描述】:

我想限制登录用户只有一个活动会话,即如果用户使用新的 sessionid 登录,旧的会话应该被终止。 我已经在 SO 上找到了很多帮助: herehere

我实现了中间件解决方案,并进行了一些额外的检查...

class OnlyOneUserMiddleware(object):
"""
Middleware to ensure that a logged-in user only has one session active.
Will kick out any previous session. 
"""
def process_request(self, request):
    if request.user.is_authenticated():
        try:
            cur_session_key = request.user.get_profile().session_key
            if cur_session_key and cur_session_key != request.session.session_key:
                # Default handling... kick the old session...
                Session.objects.get(session_key=cur_session_key).delete()
            if not cur_session_key or cur_session_key != request.session.session_key:
                p = request.user.get_profile()
                p.session_key = request.session.session_key
                p.save()
        except ObjectDoesNotExist:
            pass

到目前为止,一切都很好......在 Django 开发服务器(manage.py runserver)上一切正常,它踢掉了旧会话......

...但是当使用 Apache (with mod_wsgi) 时,它不起作用!

我试图找到有关此的任何信息,但到目前为止还没有运气......

我找到的最接近的是this,但这是一种“相反”的问题......

任何帮助将不胜感激。

编辑:我在删除会话之前添加了一个调试打印... 这是来自 Apache 的 error.log 的 sn-p:

[Fri Jan 20 09:56:50 2012] [error] old key = f42885ccb7f33b6afcb2c18fca14f44a
[Fri Jan 20 09:56:50 2012] [error] new key = ce4cfb672e6025edb8ffcd0cf2b4b8d1
[Fri Jan 20 09:57:14 2012] [error] old key = f42885ccb7f33b6afcb2c18fca14f44a
[Fri Jan 20 09:57:14 2012] [error] new key = 0815c56241ac21cf4b14b326f0aa7e24

前两个谎言来自我使用第一个会话(Firefox)进入时

最后两个是从我进入第二个会话(Chromium)时开始的

...原来旧的Session记录没有被删除...???

我正在运行与使用 devserver 完全相同的 PostgreSQL 实例...

Edit2:原来我的代码有问题......当在 Session 中找不到新的 Session_key 时它失败了......

这里是固定代码... try..except 现在在正确的位置

class OnlyOneUserMiddleware(object):
    """
    Middleware to ensure that a logged-in user only has one session active.
    Will kick out any previous session. 
    """
    def process_request(self, request):
        if request.user.is_authenticated():
            cur_session_key = request.user.get_profile().session_key
            if cur_session_key and cur_session_key != request.session.session_key:
                # Default handling... kick the old session...
                try:
                    s = Session.objects.get(session_key=cur_session_key)
                    s.delete()
                except ObjectDoesNotExist:
                    pass
            if not cur_session_key or cur_session_key != request.session.session_key:
                p = request.user.get_profile()
                p.session_key = request.session.session_key
                p.save()

【问题讨论】:

  • 当您说“不起作用”时,究竟是什么不起作用?您还在数据库中看到旧会话吗?如果您在Session 删除之前进行打印/记录调用,您是否看到在mod_wsgi 下执行?
  • @AdamKG:感谢您为我指明正确的方向!
  • 如果会话引擎是cache_db,我想我们也需要手动从缓存中移除sessionkey吧?
  • user.get_profile() 是做什么的?

标签: python django apache2 mod-wsgi django-authentication


【解决方案1】:

确实到处都有很多类似的问题,但这是我的解决方案。

当用户登录时,检查所有活动会话并删除具有相同user.id 的会话。对于较小的网站,这应该没问题。

# __init__.py
# Logs user out from all other sessions on login, django 1.8

from django.contrib.sessions.models import Session
from django.contrib.auth.signals import user_logged_in
from django.db.models import Q
from django.utils import timezone

def limit_sessions(sender, user, request, **kwargs):
    # this will be slow for sites with LOTS of active users

    for session in Session.objects.filter(
        ~Q(session_key = request.session.session_key),
        expire_date__gte = timezone.now()
    ):
        data = session.get_decoded()
        if data.get('_auth_user_id', None) == str(user.id):
            # found duplicate session, expire it
            session.expire_date = timezone.now()
            session.save()

    return

user_logged_in.connect(limit_sessions)

【讨论】:

  • 你比较过两种方式的性能吗?我的意思是,通过信号和中间件。如果使用中间件,所有请求都应该通过handle_request。如果使用信号,虽然只有登录事件命中函数,但它会迭代会话表......所以,考虑一下性能,哪个更好......
  • 我确实说过“对于较小的网站”:) - 您可能想像 OP 一样存储密钥,但只在登录时检查它,就像我为获得更高性能的解决方案所做的那样
  • 是的,我看到你提到过小型网站 :-) 我只是想知道什么对大型网站更好 :-)
【解决方案2】:

虽然不推荐,但您始终可以使用这种方法,但它确实有效。

my_old_sessions = Session.objects.all()
for row in my_old_sessions:
   if row.get_decoded().get("_username") == request.user.username:
      row.delete()

您可以在验证用户之前在 login() 函数中实现上述代码。

这当然只有在你有一个 login() 函数方法在他的会话中存储 USERS 用户名时才有效,如下所示:

request.session["_username"] = request.user.username

如果您使用这种方法,请记住在进行这些更改后在运行服务器之前清空所有会话的数据库,因为它会引发 KeyLookUp 错误。

【讨论】:

  • _username 不再在会话对象的 get_decoded() (Django 1.8) 中。但是,user_id 是,所以row.get_decoded().get("_auth_user_id") == request.user.pk 可以工作。
【解决方案3】:

我觉得 django.contrib.auth 信号可以在这里提供帮助。登录时,使旧用户会话无效。

【讨论】:

  • 如果您已经在使用用户数据库,这意味着必须先注销所有用户才能使用。
猜你喜欢
  • 2013-11-10
  • 2019-08-22
  • 2012-06-02
  • 1970-01-01
  • 2014-10-11
  • 2015-03-12
  • 2019-10-12
  • 2015-11-03
  • 1970-01-01
相关资源
最近更新 更多