如何防止在 Django 中多次登录

fns*_*sne 6 django session

我正在编写一个无法同时登录的用户系统。如果该帐户在某处处于登录状态,而有人在其他位置登录同一帐户。后一个将被登录。而前一个将被注销。我正在使用一个与用户模型关联的 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)
Run Code Online (Sandbox Code Playgroud)

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")
Run Code Online (Sandbox Code Playgroud)

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

Wil*_*sem 14

我认为你让事情变得非常复杂,通过将数据存储在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)
Run Code Online (Sandbox Code Playgroud)

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

  1. 我们删除所有处于活动状态的Sessions(和相应的UserSessions)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
    )
Run Code Online (Sandbox Code Playgroud)