如何检测Django Web应用程序在不同位置的多个登录?

32

我希望在我的Django应用中,对于一个登录账户只允许同时存在一个经过身份验证的会话。因此,如果一个用户在特定IP地址上登录了网页,并且这些相同的用户凭据被用于从另一个IP地址登录,我想要对其进行处理(可以注销第一个用户或拒绝第二个用户的访问)。

4个回答

24

不确定是否仍需要,但我想分享我的解决方案:

1)安装django-tracking(感谢Van Gale的提示,Google Maps + GeoIP真是太棒了!)

2)添加此中间件:

from django.contrib.sessions.models import Session
from tracking.models import Visitor
from datetime import datetime

class UserRestrictMiddleware(object):
    """
    Prevents more than one user logging in at once from two different IPs
    """
    def process_request(self, request):
        ip_address = request.META.get('REMOTE_ADDR','')
        try:
            last_login = request.user.last_login
        except:
            last_login = 0
        if unicode(last_login)==unicode(datetime.now())[:19]:
            previous_visitors = Visitor.objects.filter(user=request.user).exclude(ip_address=ip_address)
            for visitor in previous_visitors:
                Session.objects.filter(session_key=visitor.session_key).delete()
                visitor.user = None
                visitor.save()

3) 确保它在 VisitorTrackingMiddleware 之后,这样当有新用户登录时,您应该会发现以前的登录自动被覆盖 :)


1
在这么多年的不活跃之后,我们还能相信django-tracking吗? - katsos

10
如果您已经像建议中那样使用django-tracking,那么实现此操作的方法要简单得多:

定义一个信号处理程序:

# myapp/signals.py
def kick_my_other_sessions(sender, request=None, user=None, **kwargs):
    from tracking.models import Visitor
    from django.contrib.sessions.models import Session
    keys = [v.session_key for v in Visitor.objects.filter(user=request.user).exclude(session_key=request.session.session_key)]
    Session.objects.filter(session_key__in=keys).delete()
创建一个针对"user_logged_in"信号的监听器:
# myapp/__init__.py
from myapp.signals import kick_my_other_sessions
from django.contrib.auth.signals import user_logged_in
user_logged_in.connect(kick_my_other_sessions, sender=User)

这将会实施一种“最后登录的用户获胜”的系统。如果您想允许同一IP下同一用户的多次登录,则可以在Visitors查找中添加一个.exclude()


为了避免在每次登录时仍然找到已删除会话的访问者,还可以设置visitor.user = None以匹配每个访问者。这很重要,如果您决定添加一条消息告诉新登录的用户“您刚刚被注销了另一个会话”。 - Duncan

7
Django的中间件可能会帮助您实现此目标。问题在于,您可能希望允许来自同一IP地址的多个匿名会话,甚至不同用户的已认证会话,但不允许同一用户的已认证会话。
您需要执行以下操作:
  1. 创建一个用户配置文件模型,用于存储用户最后一次登录的IP地址。请参阅Django的关于用户存储其他信息文档。

  2. 实现一个自定义身份验证后端。当此后端触发并成功验证用户(只需调用super)时,将删除用户配置文件模型中的用户最后一次登录IP。

  3. 实现Django的django.contrib.sessions.SessionMiddleware类的子类。实现process_request。如果request.user对象的配置文件模型没有IP地址,则设置它并允许请求。如果它具有IP,并且该IP与当前请求的IP(request.META.REMOTE_ADDR)不同,则可以执行您喜欢的任何操作,以注销其他用户或向请求方返回错误。

  4. 更新您的settings.py文件,以使您的自定义身份验证后端首先处理,并使您的自定义会话中间件也首先处理。这涉及更新settings.AUTHENTICATION_BACKENDSsettings.MIDDLEWARE_CLASSES


如果你只限制IP,那么用户有可能会在公司内共享他/她的登录信息,并且由于IP伪装,他们会来自相同的源地址。因此,对于某些用例来说,这并不足够严格,无法防止多次使用。 - Csaba Toth

6
您需要使用自定义中间件来完成此操作。
在您的中间件process_request()方法中,您将可以访问请求对象,因此您可以执行以下操作:
session_key = request.session.session_key
ip_address = request.META.get('REMOTE_ADDR', '')

现在你已经知道了IP地址,所以检查一下你创建的模型,它大致应该是这样的:
class SessionIPS(models.Model):
    session = models.ForeignKey(Session)
    IP = models.CharField(max_length=20)

所以当创建或删除会话时,您将相应地修改会话IP表,并在请求到来时确保IP地址未用于另一个会话。如果是,则从中间件返回Http404(或类似的内容)。可以显示更多细节(甚至包括自己模型中的IP地址)的可插拔应用程序是django-tracking。请参阅django-tracking

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接