首页 > 解决方案 > 如何防止在Django中多次登录

问题描述

我正在编写一个无法同时登录的用户系统。如果帐户在某处处于登录状态,而有人在其他位置登录同一帐户。后一个将被登录。前一个将被注销。我正在使用与用户模型关联的 oneToOneField 模型,并保存该用户的会话 ID。代码如下所示。

from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
from .myModels import JSONField

class Profile(models.Model):
    user = models.OneToOneField(User, models.CASCADE)
    sessionids = JSONField(null=True)


@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)

JSONField 是一个使用 textField 来存储 JSON 字符串的字段。当用户登录时,我会获取该用户的所有会话 ID 并删除所有会话 ID。然后我将当前会话 ID 添加到配置文件中。通过这样做,我可以在以前的位置注销。代码如下。

def login(request):
    if request.method == "POST":
        if request.user.is_authenticated:
            return HttpResponse("the user session is authenticated.")

        username = request.POST.get('username', '')
        password = request.POST.get('password', '')

        user = auth.authenticate(username=username, password=password)

        if user is not None and user.is_active:
            auth.login(request, user)

            #remove cur user all sessions
            sessionidsToDelete = request.user.profile.sessionids
            if sessionidsToDelete != None:
                sessions = Session.objects.filter(session_key__in=sessionidsToDelete)
                for session in sessions:
                    session.delete()

            #add cur user sessions
            sessionidsToStore = user.profile.sessionids
            print("sessionidsToStore = ")
            print(sessionidsToStore)
            print("sessionidsToDelete = ")
            print(sessionidsToDelete)

            if sessionidsToStore== None:
                sessionidsToStore = []
            else:
                sessionidsToStore = list(set(sessionidsToStore) - set(sessionidsToDelete))
            print("sessionidsToStore = ")
            print(sessionidsToStore)
            sessionidsToStore.append(request.session.session_key)
            user.profile.sessionids = json.dumps(sessionidsToStore)
            user.profile.save()

            rotate_token(request)
            return HttpResponse("login sucessful")
        elif user.is_active == False:
            userNotActivedHttpresponse = HttpResponse()
            userNotActivedHttpresponse.status_code = 605
            userNotActivedHttpresponse.reason_phrase = "This user not active"
            return userNotActivedHttpresponse
        else:
            return HttpResponse("Please Input the correct username and password")
    else:
        return HttpResponseBadRequest("Please use POST to login")

但我认为会发生一些事情。当有两个人想同时登录同一个帐户时。例如,有两个人知道同一个帐户。他们同时登录。在 A 删除所有其他会话 id 之后,B 可能会将 B 的会话 id 附加到 Profile 中。在这种情况下,A 和 B 仍然处于登录状态,不会退出。我怎样才能防止这个问题?

标签: djangosession

解决方案


我觉得你把事情搞的很复杂,把数据存到UserProfiles里面等等然后有信号,你引入了很多层级,而在每个层级,事情都可能出错。

我们在这里基本上需要两件事:一个将Users 映射到其相应设置的表。我们可以用一个UserSession模型来实现它:

# models.py

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

class UserSession(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    session = models.OneToOneField(Session, on_delete=models.CASCADE)

所以对象在和sUserSession之间建立了联系。现在我们可以实现一个登录钩子:一个在用户登录时触发的信号。在这种情况下,我们执行两件事:UserSession

  1. 我们删除所有活跃的Sessions(和相应UserSession的 s) ;User
  2. 我们创建一个新的Session和对应UserSession的,我们可以稍后删除。喜欢:
from django.contrib.auth import user_logged_in
from django.dispatch.dispatcher import receiver

@receiver(user_logged_in)
def remove_other_sessions(sender, user, request, **kwargs):
    # remove other sessions
    Session.objects.filter(usersession__user=user).delete()
    
    # save current session
    request.session.save()

    # create a link from the user to the current session (for later removal)
    UserSession.objects.get_or_create(
        user=user,
        session_id=request.session.session_key
    )

推荐阅读