Как ограничить количество одновременных пользователей, регистрирующихся в одной учетной записи в Django
Мой сайт - сайт цифрового рынка, написанный на Django.
Цифровой контент (текст, изображения, видео) на сайте по умолчанию заблокирован. Только пользователи, которые купили этот контент, могут просматривать его.
Там рассказ о том, что определенный пользователь (который купил контент) бесплатно дает имя пользователя и пароль многим людям (например, более 1000 человек в группах Facebook). Эти 1000 пользователей могут войти в систему с использованием этого единственного имени пользователя/пароля и просмотреть "заблокированный" цифровой контент, не заплатив цент.
Можно ли ограничить количество одновременных входных данных на одну учетную запись?
Я нашел этот пакет:
https://github.com/pcraston/django-preventconcurrentlogins
но то, что он делает, регистрирует предыдущий пользователь, когда кто-то вошел в систему, используя одно и то же имя пользователя/пароль. Это не поможет, потому что каждый пользователь должен каждый раз вводить имя пользователя/пароль для доступа к "заблокированному" контенту.
Ответы
Ответ 1
Чтобы ограничить одновременных пользователей, следите за существующим sessions.
В вашем текущем подходе, когда пользователь входит в систему, создается новый сеанс. Этот новый сеанс сосуществует со старыми сеансами, поэтому вы одновременно выполняете одновременные сеансы N.
Вы хотите разрешить сеанс одиночный. Самый простой способ заключается в том, чтобы аннулировать старый сеанс, когда происходит новый вход:
Другие (более полные, но более сложные) подходы будут использовать двухфакторную аутентификацию, блокировку на 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
, вы можете перечислить предыдущие сеансы и попросить пользователя выбрать, какие сеансы выходить из системы. (Не мешайте ему войти в систему, иначе он может быть заблокирован - если у него нет доступа к его предыдущим сеансам сейчас)