如何限制Django中登录同一帐户的并发用户数
How to limit number of concurrent users logging in to same account in Django
我的网站是一个用 Django 编写的数字市场网站。
网站上的数字内容(文本、图像、视频)默认为 'locked'。只有购买了这些内容的用户才能查看。
有一个故事说某些用户(购买内容的人)免费向许多人(例如 Facebook 群组中的 1,000 多人)赠送 username/password。然后,这 1,000 名用户可以使用那个 username/password 登录并查看 'locked' 数字内容,而无需支付一分钱。
是否可以限制同一个账号的同时登录数?
我找到了这个包裹:
https://github.com/pcraston/django-preventconcurrentlogins
但它的作用是在有人使用相同 username/password 登录时注销以前的用户。这无济于事,因为每个用户每次只需输入 username/password 即可访问 'locked' 内容。
要限制并发用户,请关注现有sessions。
在您当前的方法中,当用户登录时,会创建一个新会话。该新会话与旧会话共存,因此您同时拥有 N 个并发会话。
您想要允许 单个 会话。最简单的方法是在发生新登录时使旧会话无效:
- detect/extend 登录事件(使用“user_logged_in”信号)
- 对于每次登录,删除同一用户的其他现有会话(参见 "Clearing the session store")
其他(更完整但更复杂)的方法是使用 Two-factor authentication、按 IP 阻止、限制登录事件、要求电子邮件确认等...
在另一个模型中存储用户会话映射。
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
,可以列出之前的session,让用户选择退出哪些session。 (不要阻止他登录,否则他可能会被锁定 - 如果他现在无法访问之前的会话)
1 在您的 users/profiles 应用中添加一个管理命令文件
要添加管理命令,请遵循以下指南:
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 脚本来完成,如果需要请寻求帮助
我的网站是一个用 Django 编写的数字市场网站。
网站上的数字内容(文本、图像、视频)默认为 'locked'。只有购买了这些内容的用户才能查看。
有一个故事说某些用户(购买内容的人)免费向许多人(例如 Facebook 群组中的 1,000 多人)赠送 username/password。然后,这 1,000 名用户可以使用那个 username/password 登录并查看 'locked' 数字内容,而无需支付一分钱。
是否可以限制同一个账号的同时登录数?
我找到了这个包裹:
https://github.com/pcraston/django-preventconcurrentlogins
但它的作用是在有人使用相同 username/password 登录时注销以前的用户。这无济于事,因为每个用户每次只需输入 username/password 即可访问 'locked' 内容。
要限制并发用户,请关注现有sessions。
在您当前的方法中,当用户登录时,会创建一个新会话。该新会话与旧会话共存,因此您同时拥有 N 个并发会话。
您想要允许 单个 会话。最简单的方法是在发生新登录时使旧会话无效:
- detect/extend 登录事件(使用“user_logged_in”信号)
- 对于每次登录,删除同一用户的其他现有会话(参见 "Clearing the session store")
其他(更完整但更复杂)的方法是使用 Two-factor authentication、按 IP 阻止、限制登录事件、要求电子邮件确认等...
在另一个模型中存储用户会话映射。
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
,可以列出之前的session,让用户选择退出哪些session。 (不要阻止他登录,否则他可能会被锁定 - 如果他现在无法访问之前的会话)
1 在您的 users/profiles 应用中添加一个管理命令文件
要添加管理命令,请遵循以下指南: 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 脚本来完成,如果需要请寻求帮助