如何在Django中限制同时登录同一账户的用户数量

13

我的网站是一个用Django编写的数字市场网站。

该网站上的数字内容(文本、图像、视频)默认为“已锁定”。只有购买了这些内容的用户才能查看。

有一个故事是,某个用户(购买了内容的用户)免费向许多人(例如Facebook群组中的1,000多人)提供用户名/密码。然后那1,000个用户可以使用该单一的用户名/密码登录并查看“已锁定”的数字内容而不需要支付一分钱。

是否可能限制同一帐户的并发登录次数?

我找到了这个包:

https://github.com/pcraston/django-preventconcurrentlogins

但它所做的是在有人使用相同的用户名/密码登录时注销先前的用户。这样做无济于事,因为每个用户只需每次输入用户名/密码即可访问“已锁定”的内容。


请尝试查看 https://github.com/sobotklp/django-throttle-requests - Sardorbek Imomaliev
嗨,谢谢。我从未听说过这个。我会调查一下,看看它是否对我的情况有所帮助。 - Kitti Wateesatogkij
嗨。我访问了文档,似乎没有限制每个用户帐户请求次数的规则。你有什么例子可以分享吗?谢谢! - Kitti Wateesatogkij
3个回答

12

为了限制并发用户,请关注现有的会话

在您目前的方法中,当用户登录时,会创建一个新的会话。该新会话与旧会话共存,因此您在同一时间拥有N个并发会话。

您想要允许单个会话。最简单的方法是在发生新的登录时使旧会话无效:

其他(更完整但更复杂的)方法包括使用双因素身份验证、按IP阻止、限制登录事件、要求电子邮件确认等...


3

将用户会话映射存储在另一个模型中。

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

class UserSessions(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='user_sessions')
    session = models.OneToOneField(Session, related_name='user_sessions',
                                   on_delete=models.CASCADE)

    def __str__(self):
        return '%s - %s' % (self.user, self.session.session_key)

如果您有自己的登录视图,您可以自行更新此模型:
from django.contrib.auth.views import login as auth_login

def login(request):
    auth_login(request)
    if request.user.is_authenticated():
        session = Session.objects.get(session_key=request.session.session_key)
        user_session = UserSession.objects.create(user=request.user, session=session)
    no_of_logins = request.user.user_sessions.count()
    if no_of_logins > 1:  # whatever your limit is
        request.SESSION['EXTRA_LOGIN'] = True
        # Do your stuff here

另一个选项是使用 Signal。Django 提供了信号:user_logged_inuser_login_faileduser_logged_out,如果你使用的是 Django 的登录视图。

# signals.py
from django.contrib.auth.signals import user_logged_in
from django.dispatch import receiver

@receiver(user_logged_in)
def concurrent_logins(sender, **kwargs):
    user = kwargs.get('user')
    request = kwargs.get('request')
    if user is not None and request is not None:
        session = Session.objects.get(session_key=request.session.session_key)
        UserSessions.objects.create(user=user, session=session)
    if user is not None:
        request.session['LOGIN_COUNT'] = user.user_sessions.count()

# your login view
def login(request):
     auth_login(request)
     if request.user.is_authenticated() and request.session['LOGIN_COUNT'] > 1:
         # 'LOGIN_COUNT' populated by signal
         request.session['EXTRA_LOGIN'] = True
         # Do your stuff

如果 EXTRA_LOGINTrue,您可以列出之前的会话,并要求用户选择注销哪些会话。请不要阻止他登录,否则他可能会被锁定 - 如果他现在无法访问以前的会话。

3

1 在您的用户/配置文件应用程序中添加管理命令文件

要添加管理命令,请按照以下步骤操作: https://docs.djangoproject.com/en/1.10/howto/custom-management-commands/

2 管理命令代码: 杀死所有拥有超过10个会话的用户的会话,如果需要,您可以将该值更改为1K,或将此值作为参数发送到管理命令。

from django.core.management.base import BaseCommand, CommandError
from django.contrib.sessions.models import Session
from django.contrib.auth.models import User

class Command(BaseCommand):
    def handle(self, *args, **options):
        session_user_dict = {}


        # users with more than 10 sessions - del all

        for ses in Session.objects.all():
            data = ses.get_decoded()
            user_owner = User.objects.filter(pk = data.get('_auth_user_id', None))

            if int(data.get('_auth_user_id', None)) in session_user_dict:
                session_user_dict[int(data.get('_auth_user_id', None))] += 1
            else:
                session_user_dict[int(data.get('_auth_user_id', None))] = 1

        for k,v in session_user_dict.iteritems():
            if v > 10:
                for ses in Session.objects.all():
                    data = ses.get_decoded()
                    if str(k) == data.get('_auth_user_id', None):
                        ses.delete()

3. 可选的密码更改 - 在结束恶意用户会话后,将该用户的密码更改为不同的密码。 要实现此操作,请更改上述代码中的最后一个循环。

            for k,v in session_user_dict.iteritems():
            if v > 10:
                for ses in Session.objects.all():
                    data = ses.get_decoded()
                    if str(k) == data.get('_auth_user_id', None):
                        ses.delete()
                        theuser =  User.objects.filter(pk=k)
                        #maybe use uuid to pick a password ...
                        theuser.set_password('new_unknown_password')

4. 添加一个 Django 管理命令到 crontab 中,每分钟/小时或其他时间,请参考以下指南:https://www.cyberciti.biz/faq/how-do-i-add-jobs-to-cron-under-linux-or-unix-oses/

如果您正在使用虚拟环境,请注意从cron运行的管理命令需要先进入虚拟环境。您可以使用.sh脚本完成此操作,如果需要帮助,请咨询相关人员。


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