Django5+Vue3 系列文章


前言

此项目采用 Django 框架的 5.0.7 版本进行开发。
Django 5.0 支持的 Python 版本为 3.10、3.11 和 3.12。
CSDN 专栏链接: ~快捷传送门: 留个赞再走呗 😭! ~

登录验证流程:使用 Views 视图函数进行数据校验

打开 apps/oaauth/views.py,定义视图函数

from django.shortcuts import render
from rest_framework.views import APIView
from apps.oaauth.serializers import LoginSerializer, UserSerializer
from datetime import datetime
from .authentications import generate_jwt
from rest_framework import status
from rest_framework.response import Response

# Create your views here.


class LoginView(APIView):
    # 登录只能用post方法向服务端发送请求
    def post(self,request):
        # 1.验证数据是否可用,在此使用序列化校验数据库与客户端发送到数据
        serializer = LoginSerializer(data=request.data)
        # 如果验证成功
        if serializer.is_valid():
            # 间接获取数据库数据(attrs['user'] = user)
            user = serializer.validated_data.get('user')
            # 记录上次登陆的时间
            user.last_login = datetime.now()
            user.save()
            # 登陆成功,生成并返回JWT Token
            token = generate_jwt(user)
            return Response({'token': token, 'user': UserSerializer(user).data})
        # 身份验证失败
        else:
            print(serializer.errors)
            return Response({"message": "参数验证失败!"}, status=status.HTTP_400_BAD_REQUEST)

在定义登录的视图函数时,身份验证是一个关键步骤,通常我们会使用序列化器来处理数据。为此,我们需要创建一个专门的序列化器来进行身份验证。
序列化器: 通过定义数据模型的规则和验证逻辑,确保接收到的数据符合预期格式并进行安全检查。

数据验证自动化:Serializer 序列化器的应用

定义 LoginSerializer 并对 validate 方法进行重写

创建 apps/oaauth/serializers.py 文件,自定义序列化器

from rest_framework import serializers
from .models import OAUser, UserStatusChoices, OADepartment

class LoginSerializer(serializers.Serializer):
    # 定义需要验证的字段
    email = serializers.EmailField(required=True)  # 电子邮箱字段,必填,验证为有效的邮箱格式
    password = serializers.CharField(max_length=20, min_length=6)  # 密码字段,必填,要求最少6个字符且最多20个字符

    # 重写validate方法,以实现自定义校验功能
    def validate(self, attrs):
        # attrs 包含 email 和 password 数据,从中获取这些数据
        email = attrs.get('email')
        password = attrs.get('password')

        # 验证成功的情况
        if email and password:
            # 根据 email 查询用户
            user = OAUser.objects.filter(email=email).first()  # 尝试获取对应邮箱的用户对象
            # 检查用户是否存在
            if not user:
                # 如果用户不存在,抛出验证错误
                raise serializers.ValidationError("请输入正确的邮箱!")

            # 用户存在,校验密码
            if not user.check_password(password):  # 使用 Django 自带的方法检查密码
                # 如果密码不正确,抛出验证错误
                raise serializers.ValidationError("请输入正确的密码!")

            # 判断用户状态是否可用
            if user.status == UserStatusChoices.UNACTIVE:  # 用户状态为未激活
                raise serializers.ValidationError("该用户尚未激活")
            elif user.status == UserStatusChoices.LOCKED:  # 用户状态为锁定
                raise serializers.ValidationError("该账号已被锁定,请联系管理员!")

            # 视图函数中还会做登录操作,为了节省执行 SQL 语句的次数,将用户对象直接放入 attrs 中,方便在视图中使用
            attrs['user'] = user
        else:
            # 如果没有提供邮箱或密码,抛出验证错误
            raise serializers.ValidationError("请传入邮箱和密码!")

        # 返回包含验证后数据的字典
        return attrs

序列化模型定义与模型嵌套

代码续写如下(示例):

# 继承ModelSerializer模型, 避免重复定义
class DepartmentSerializer(serializers.ModelSerializer):
    class Meta:
        model = OADepartment
        fields = '__all__'


class UserSerializer(serializers.ModelSerializer):
    # 重写department
    department = DepartmentSerializer()

    class Meta:
        model = OAUser
        # fields = "__all__"
        # 排除不需要返回的字段(重要信息以及无需返回的数据)
        exclude = ("password", "groups", "user_permissions")

JWT Token 权限认证

创建 apps\oaauth\ authentications.py 文件

使用 JWT Token 进行验证的好处在于它提供了一种自包含、安全且易于在不同服务间传递用户身份信息的方式,同时减少了对服务器的依赖,提高了系统的扩展性和响应速度。

import jwt
import time
from django.conf import settings
from rest_framework.authentication import BaseAuthentication, get_authorization_header
from rest_framework import exceptions
from jwt.exceptions import ExpiredSignatureError
from .models import OAUser


def generate_jwt(user):
    """
    生成JWT Token,用于身份验证。
    参数:
    user (OAUser): 当前登录的用户对象,通常是经过身份验证后的用户
    返回:
    str: 生成的JWT Token,这个Token包含用户的唯一标识和过期时间
    """
    # 设置Token的过期时间为7天后
    expire_time = time.time() + 60 * 60 * 24 * 7
    # 创建JWT Token,包含用户ID和过期时间,使用SECRET_KEY进行签名
    return jwt.encode({'userid': user.pk, 'exp': expire_time}, key=settings.SECRET_KEY, algorithm='HS256')


class JWTAuthentication(BaseAuthentication):
    """
    自定义JWT认证类,用于在Django REST Framework中进行JWT认证。
    这个类将处理从请求中提取JWT,验证其有效性,并返回对应的用户对象。
    """
    keyword = 'JWT'  # 认证头部关键字,通常是'JWT'

    def authentication(self, request):
        """
        从请求对象中提取并验证JWT Token,以进行身份验证。
        参数:
        request (HttpRequest): HTTP请求对象,可以是来自客户端的HTTP请求
        返回:
        tuple: (user, token) 用户对象和JWT Token,如果认证失败则返回None
        """
        # 从请求头中获取Authorization字段,并按空格分割
        auth = get_authorization_header(request).split()

        # 检查Authorization头部是否存在,并且是否以'JWT'关键字开头
        if not auth or auth[0].lower() != self.keyword.lower().encode():
            return None  # 认证失败,返回None

        # 检查Token的格式是否正确,确保有且只有一个Token
        if len(auth) == 1:
            msg = '不可用的JWT请求头!'  # Token缺失信息
            raise exceptions.AuthenticationFailed(msg)  # 抛出认证失败异常
        elif len(auth) > 2:
            msg = '不可用的JWT请求头!JWT Token中间不应该有空格!'  # Token包含多余的空格
            raise exceptions.AuthenticationFailed(msg)  # 抛出认证失败异常

        try:
            # 提取Token并解码
            jwt_token = auth[1]
            # 使用SECRET_KEY解码Token
            jwt_info = jwt.decode(jwt_token, settings.SECRET_KEY, algorithms=["HS256"])
            userid = jwt_info['userid']  # 获取用户ID

            try:
                # 根据用户ID查找用户,确保用户存在
                user = OAUser.objects.get(pk=userid)
                setattr(request, 'user', user)  # 将用户对象绑定到request上,方便后续使用
                return user, jwt_token  # 返回用户对象和JWT Token

            except OAUser.DoesNotExist:
                msg = "JWT Token对应的用户不存在"  # 如果找不到用户ID
                raise exceptions.AuthenticationFailed(msg)  # 抛出认证失败异常

        except ExpiredSignatureError:
            msg = "JWT Token已过期!"  # 处理Token过期情况
            raise exceptions.AuthenticationFailed(msg)  # 抛出认证失败异常

        except jwt.DecodeError:
            msg = "无效的JWT Token"  # Token解码失败
            raise exceptions.AuthenticationFailed(msg)  # 抛出认证失败异常

路由映射

创建 apps\oaauth\ urls.py 文件

from django.urls import path
from . import views
# 应用命名空间
app_name = 'oaauth'

urlpatterns = [
    # .as_view() 方法会返回一个接受请求参数的可调用对象,这样 Django 就可以正确地将请求分派给类视图的方法
    path('login',views.LoginView.as_view(),name='login'),
]

项目根目录添加子路由

打开项目根目录的 oaback\urls.py 文件,添加子路由

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("auth/",include('apps.oaauth.urls'))
]

Postman 测试

验证:失败情况示例

验证:成功情况示例

总结

本节内容成功实现了后端的登录和身份验证功能,并通过 Postman 工具进行了全面的测试验证。