如何限制登录Django中同一帐户的并发用户数

时间:2016-11-02 11:02:01

标签: django django-admin django-authentication django-users django-sessions

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

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

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

是否可以限制同时登录同一帐户的数量?

我找到了这个包裹:

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

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

3 个答案:

答案 0 :(得分:9)

要限制并发用户,请留意现有的sessions

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

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

其他(更完整但更复杂)的方法是使用Two-factor authentication,阻止每个IP,限制登录事件,需要电子邮件确认等等......

答案 1 :(得分: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每分钟/小时或每当使用本指南时,向crontab添加django管理命令: https://www.cyberciti.biz/faq/how-do-i-add-jobs-to-cron-under-linux-or-unix-oses/

如果您使用虚拟环境,请记住从cron运行的管理命令需要先进入虚拟环境,您可以使用.sh脚本执行此操作,如果需要请求帮助

答案 2 :(得分:2)

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

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,您可以列出之前的会话,并要求用户选择要注销的会话。 (不要阻止他登录,否则他可能会被锁定 - 如果他现在无法访问他之前的会话)