Как ограничить количество одновременных пользователей, регистрирующихся в одной учетной записи в Django

Мой сайт - сайт цифрового рынка, написанный на Django.

Цифровой контент (текст, изображения, видео) на сайте по умолчанию заблокирован. Только пользователи, которые купили этот контент, могут просматривать его.

Там рассказ о том, что определенный пользователь (который купил контент) бесплатно дает имя пользователя и пароль многим людям (например, более 1000 человек в группах Facebook). Эти 1000 пользователей могут войти в систему с использованием этого единственного имени пользователя/пароля и просмотреть "заблокированный" цифровой контент, не заплатив цент.

Можно ли ограничить количество одновременных входных данных на одну учетную запись?

Я нашел этот пакет:

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

но то, что он делает, регистрирует предыдущий пользователь, когда кто-то вошел в систему, используя одно и то же имя пользователя/пароль. Это не поможет, потому что каждый пользователь должен каждый раз вводить имя пользователя/пароль для доступа к "заблокированному" контенту.

Ответ 1

Чтобы ограничить одновременных пользователей, следите за существующим sessions.

В вашем текущем подходе, когда пользователь входит в систему, создается новый сеанс. Этот новый сеанс сосуществует со старыми сеансами, поэтому вы одновременно выполняете одновременные сеансы N.

Вы хотите разрешить сеанс одиночный. Самый простой способ заключается в том, чтобы аннулировать старый сеанс, когда происходит новый вход:

  • определить/расширить событие входа (используйте сигнал user_logged_in
  • для каждого входа, удалите другие существующие сеансы от одного и того же пользователя (см. "Очистка хранилища сеансов" )

Другие (более полные, но более сложные) подходы будут использовать двухфакторную аутентификацию, блокировку на IP, дросселирование события входа, требующее подтверждение по электронной почте и т.д.

Ответ 2

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 Дополнительное изменение пароля - после убийства сеансов плохих пользователей - замените пароль плохих пользователей на diff. Для этого измените последний цикл в приведенном выше коде

            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/

если вы используете виртуальный env, помните, что команда управления, которая запускается из cron, должна сначала войти в виртуальный env, вы можете сделать это с помощью .sh script, при необходимости обратиться за помощью

Ответ 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_in, user_login_failed и user_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_LOGIN - True, вы можете перечислить предыдущие сеансы и попросить пользователя выбрать, какие сеансы выходить из системы. (Не мешайте ему войти в систему, иначе он может быть заблокирован - если у него нет доступа к его предыдущим сеансам сейчас)