Django DRF + SimpleJWT 实战 (一):基于纯自定义 Model 的 Token 鉴权与踩坑指南

1.环境准备

第一步,我们需要安装Django中处理API、JWT和跨域的核心库

1.API:RESTful规范。安装库 pip install django djangorestframework

  • djangorestframework (DRF): 强大的 API 开发框架。

2.JWT:pip install djangorestframework-simplejwt

  • djangorestframework-simplejwt : 专门为 DRF 设计的 JWT 插件,帮我们搞定 Token 生成、验证和刷新。

3.跨域:pip install django-cors-headers

  • django-cors-headers : 处理前端跨域请求(前端在 localhost:5173,后端在 localhost:8000,必须配置这个)。

第二步,创建Django项目,创建app执行相应登录、注册、token刷新机制。

在工具中点击:

输入指令startapp user。在这里创建的app,会自动在app列表中帮我们添加

如果是手动在终端输入指令 python manage.py startapp user,就需要我们手动的在app列表中添加对应app。

2.设计用户模型

注意 :本教程为了演示原理,密码采用了 明文存储 。在生产环境中, 严禁 明文存储密码!请务必使用 Django 自带的 make_password 或 set_password 进行哈希加密存储。

打开 backend/user/models.py :

方法1:使用 @property 来添加DRF框架所需要的字段

python 复制代码
from django.db import models


class UserInfo(models.Model):
    # 由于指定了AUTH_USER_MODEL
    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = [] # 创建超级用户时需要的必填字段(除了 username 和 password)

    username = models.CharField(max_length=32,unique= True)
    password = models.CharField(max_length=32)
    email = models.EmailField(unique= True,null=True)
    age = models.IntegerField(null=True)
    sex = models.CharField(max_length=32,null=True)
    status = models.IntegerField(
        null=True,
        choices=[(0, '正常'), (1, '停用')],
        default=0,
        verbose_name="帐号状态(0正常 1停用)",
        db_comment="帐号状态(0正常 1停用)"
    )

    # 添加 simplejwt 所需的属性
    @property
    def is_active(self):
        # 0 表示正常,1 表示停用
        return self.status == 0

    @property
    def is_authenticated(self):
        return True

    @property
    def is_anonymous(self):
        return False

    class Meta:
        db_table = 'userinfo'
        verbose_name = '用户信息'
        verbose_name_plural = '用户信息'

方法2:直接在数据库字段中定义这些属性

python 复制代码
from django.db import models

class UserInfo(models.Model):
    # 由于指定了AUTH_USER_MODEL
    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = [] # 创建超级用户时需要的必填字段(除了 username 和 password)

    username = models.CharField(max_length=32, unique=True)
    password = models.CharField(max_length=32)
    email = models.EmailField(unique=True, null=True)
    age = models.IntegerField(null=True)
    sex = models.CharField(max_length=32, null=True)
    
    # 【改动点1】直接在数据库中定义 is_active
    # 替代了原来的 status 字段,更符合 Django 原生规范
    is_active = models.BooleanField(
        default=True, 
        verbose_name="账号状态",
        help_text="指明用户是否被视为活跃。以反选代替删除账号。"
    )

    # 【改动点2】is_authenticated 和 is_anonymous 不需要存数据库
    # 它们描述的是"当前对象的身份状态",恒定逻辑如下:
    
    @property
    def is_authenticated(self):
        """
        总是返回 True。这是告诉 Django:"只要你拿到了这个 UserInfo 实例,
        说明这肯定是一个已登录的合法用户。"
        """
        return True

    @property
    def is_anonymous(self):
        """
        总是返回 False。这是告诉 Django:"这不是一个匿名游客对象。"
        """
        return False

    class Meta:
        db_table = 'userinfo'
        verbose_name = '用户信息'
        verbose_name_plural = '用户信息'

对于3个方法的解释:

这三个属性是 Django 认证系统(Authentication System)的核心协议,DRF 和 SimpleJWT 都是基于这个协议来工作的。

1. is_active

  • 含义 : "这个账号还能用吗?"
  • 作用 :
    • **登录拦截 :**SimpleJWT 在签发 Token 前,以及 DRF 在验证 Token 后,都会检查这个属性。如果 is_active=False ,即使用户名密码正确,或者 Token 签名正确,系统也会拒绝请求,返回 401 Unauthorized 。
    • **业务场景 :**用户离职、账号被封禁、邮箱未验证。
  • 实现建议 :
    • 方法1 :用 @property 动态判断 status == 0 。优点是不用改数据库结构,复用已有业务逻辑。
    • 方法2 :直接存 True/False 。优点是查询快,可以直接 filter(is_active=True)

2. is_authenticated

  • 含义 : "不管你是谁,你到底登录了没?"
  • 作用 :
    • **权限控制 :**DRF 的 IsAuthenticated 权限类就是靠检查 request.user.is_authenticated 来决定是否放行的。
    • 返回值 :
      • 登录用户(User 对象) :必须返回 True 。
      • 匿名用户(AnonymousUser 对象) :Django 有一个特殊的 AnonymousUser 类(代表未登录的游客),它的这个属性永远返回 False 。
  • **注意 :**这个属性绝对不应该设计成数据库字段!因为它描述的是 当前请求的状态 (Context),而不是用户的固有属性。只要是一个有效的用户实例,它天然就是"已认证实体",所以你直接返回 True 是完全正确的。Django 默认的 AbstractBaseUser 也是把它实现为一个只读属性(property),恒为 True 。

3. is_anonymous

  • 含义: "你是游客吗?"
  • 作用 :
    • 它是 is_authenticated 的反义词。
    • 登录用户 :必须返回 False 。
    • 匿名用户 :必须返回 True 。
  • 注意 : 同样不应该设计成数据库字段!这也是一个逻辑属性。对于任何真实存在的 UserInfo 对象来说,它肯定不是匿名用户,所以硬编码返回 False 是标准做法。

3.全局配置

打开 backend/backend/settings.py ,我们需要修改 5 个地方:

1.注册应用:将DRF,SimplejWT、跨域插件添加到应用列表中去。

python 复制代码
INSTALLED_APPS = [
    # ... 原有应用
    'rest_framework',              # 注册 DRF
    'rest_framework_simplejwt',    # 注册 SimpleJWT
    'corsheaders',                 # 注册跨域插件
    'user',                        # 注册我们自己的 core 应用
]

2.激活中间件

python 复制代码
MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',  # 【新增】必须放在最前面或尽量靠前
    'django.middleware.security.SecurityMiddleware',
    # ... 其他中间件
]

3.配置自定义用户模型 告诉 Django :"不要用自带的 User 了,用我写的 user.UserInfo "。

python 复制代码
AUTH_USER_MODEL = "user.UserInfo"

4.配置 DRF JWT 这里我们定义: API 默认必须登录才能访问,认证方式是 JWT。

python 复制代码
from datetime import timedelta  # 记得导入 timedelta

REST_FRAMEWORK = {
    # 默认认证方式:使用 SimpleJWT 的 JWTAuthentication
    "DEFAULT_AUTHENTICATION_CLASSES": (
        "rest_framework_simplejwt.authentication.JWTAuthentication",
    ),
    # 默认权限:允许任何人访问 (我们会在具体 View 中覆盖这个,或者设为 IsAuthenticated 默认更安全)
    # 这里为了方便注册接口,先设为 AllowAny,但在受保护接口手动加 IsAuthenticated
    "DEFAULT_PERMISSION_CLASSES": (
        "rest_framework.permissions.AllowAny",
    ),
}

SIMPLE_JWT = {
    # Access Token 有效期 5 分钟
    "ACCESS_TOKEN_LIFETIME": timedelta(minutes=5),
    # Refresh Token 有效期 7 天
    "REFRESH_TOKEN_LIFETIME": timedelta(days=7),
    # 前端请求头格式: Authorization: Bearer <token>
    # 注意 Bearer 和 <token> 之间必须有一个空格 。
    "AUTH_HEADER_TYPES": ("Bearer",),
}

5.配置跨域 ( CORS ) 允许前端访问后端。

注意的是末尾不要加斜杠 / 。这也是一个常见的坑。

python 复制代码
CORS_ALLOWED_ORIGINS = [
    "http://localhost:5173",  # Vue 默认端口
    "http://127.0.0.1:5173",
]

4.编写序列化器

序列化器 (Serializer) 的作用 :它是数据转换器。

  • 输入时 :把前端传来的 JSON (用户名、密码) 校验并转成 Python 对象。

  • 输出时 :把 Python 对象 (User) 转成 JSON 返回给前端。

在 backend/user/ 下新建 serializers.py

python 复制代码
from rest_framework import serializers
from user.models import UserInfo

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = UserInfo
        fields = ['username', 'password']  # 只需要填写用户名和密码
        extra_kwargs = {
            'password': {'write_only': True}
        }

5.编写视图

视图负责处理具体的 HTTP 请求。

打开 backend/user/views.py :

python 复制代码
from rest_framework import permissions,status
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework_simplejwt.tokens import RefreshToken

from user.models import UserInfo
from user.serializers import UserSerializer

# 注册接口
class RegisterView(APIView):
    permission_classes = [permissions.AllowAny] # 允许任何人访问
    def post(self,request):
        user = UserInfo.objects.filter(username=request.data.get('username')).first()
        if user:
            return Response({'message':"用户已存在",'code':400},status = status.HTTP_400_BAD_REQUEST)
        serializer = UserSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response({'message':"注册成功",'code':200,'data':{'username':serializer.data.get('username')}},status = status.HTTP_200_OK)
        return Response({'message':"注册失败",'code':400,'data':serializer.errors},status = status.HTTP_400_BAD_REQUEST)

#登录接口
class LoginView(APIView):
    permission_classes = [permissions.AllowAny] # 允许任何人访问
    def post(self,request):
        data = request.data
        username = data.get('username')
        password = data.get('password')

        user = UserInfo.objects.filter(username=username,password=password).first()
        if user:
            refresh = RefreshToken.for_user(user)
            token = str(refresh.access_token)
            print(token)
            return Response({'message':"登陆成功",'code':200,'data':{'token':token}},status = status.HTTP_200_OK)
        return Response({'message':"用户名或密码错误",'code':400},status = status.HTTP_400_BAD_REQUEST)
    
#测试接口
class TestView(APIView):
    permission_classes = [permissions.IsAuthenticated]
    def post(self,request):
        user = request.user
        print(user.username)
        return Response({'message':"测试成功",'code':200},status = status.HTTP_200_OK)

6.配置路由

最后,我们需要把这些视图挂载到 URL 上

1.应用级路由 ( backend/user/urls.py ,新建此文件):

python 复制代码
from django.urls import path
from .views import RegisterView, LoginView, TestView

urlpatterns = [
    path('register/', RegisterView.as_view()),
    path('login/', LoginView.as_view()),
    path('test/', TestView.as_view())
]

2.项目级路由 ( backend/backend/urls.py ):

python 复制代码
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('user/', include('user.urls'))
]

7.生成数据并运行

python 复制代码
# 1. 生成迁移文件 (检测 models 变化)
python manage.py makemigrations

# 2. 执行迁移 (创建表)
python manage.py migrate

# 3. 启动服务器
python manage.py runserver

8.效果展示(使用Apipost)

相关接口:

--注册接口:http://127.0.0.1:8000/user/register/

--登录接口:http://127.0.0.1:8000/user/login/

--测试接口:http://127.0.0.1:8000/user/test/

1.首先我们进行注册,注册需要填写用户名和密码。

可以看到我们已经注册成功,在数据库中也能找到对应的用户。

2.然后我们进行登录,登录成功之后会返回access_token,我们将该token保存起来

登录成功之后会返回access_token值,我们将该值设置在测试接口的请求头中。去进行测试。

3.测试token是否有效

要注意的是要将头部添加Bearer。我们可以看到此时测试是成功的。

接下来我们修改一下token值,也就是给一个错误的token值。

此时请求就失败了。我们在后端输出中也能看出是401错误。

然后我们再等过了超时时间进行测试,token是否会过期,这里我们设置的access_token的有效期是5分钟,等5分钟之后重新测试。

我们发现,在access_token有效期内,我们的请求都不会被拦截。但是当access_token有效期超出了我们设置的5分钟之后我们再进行请求就会发生401的错误。之后我们可以拿refresh_token去刷新我们的access_token,就不需要重新登录了(续权)。但是在这章我们没有介绍token的续权。续权操作,在前端的响应拦截器中,当识别到401错误的时候,就拿refresh_token,通相应接口来拿到新的access_token,完成token的续权。

9.小结

通过本文,我们从零开始搭建了一套基于 Django REST Framework (DRF) 和 SimpleJWT 的用户认证系统。我们不仅完成了环境配置、跨域处理,还深入探讨了自定义用户模型(Custom User Model)中 is_active 、 is_authenticated 等关键属性的底层原理,这在实际开发中是避免报错的关键细节。

核心知识点回顾:

1. SimpleJWT 集成 : 如何配置 JWT 的生命周期与认证头。
2. 自定义模型陷阱 : 在使用非标准 User 模型时,必须补全 Django 认证系统所需的协议属性(Property)。
3. 鉴权流程闭环 :从注册 -> 登录签发 Token -> 携带 Token 访问受保护接口 -> Token 校验。

虽然我们成功实现了"门禁系统"(鉴权),但这只是后端开发的起点。DRF 还有更多强大的功能等待探索,比如 视图集 (ViewSets) 如何简化代码、 权限类 (Permissions) 如何做细粒度的对象级控制、以及如何利用 Refresh Token 实现无感自动续签。这些内容我们将在后续的文章中继续深入。

需要注意的是,本实战主要演示了鉴权流程的跑通。在生产环境中,你还需要考虑:

  • 密码安全 :务必使用哈希加密存储(如 make_password )。
  • 权限控制 :结合 Permissions 实现不同角色的访问控制。
  • 体验优化 :在前端实现 Token 的自动刷新机制(token续权)。
    希望这篇文章能帮你快速搭建起安全的认证后端,为你的项目打下坚实基础!
相关推荐
二等饼干~za8986681 天前
碰一碰发视频系统源码开发搭建--技术分享
java·运维·服务器·重构·django·php·音视频
高洁011 天前
基于Tensorflow库的RNN模型预测实战
人工智能·python·算法·机器学习·django
luoluoal2 天前
基于python的RSA算法的数字签名生成软件(源码+文档)
python·mysql·django·毕业设计
牢七3 天前
5655869
django
秋氘渔4 天前
智演沙盘 —— 基于大模型的智能面试评估系统
python·mysql·django·drf
jcsx5 天前
如何将django项目发布为https
python·https·django
百锦再5 天前
京东云鼎入驻方案解读——通往协同的“高架桥”与“快速路”
android·java·python·rust·django·restful·京东云
Warren985 天前
datagrip新建oracle连接教程
数据库·windows·云原生·oracle·容器·kubernetes·django
韩立学长5 天前
【开题答辩实录分享】以《跳蚤市场二手物品交易推荐平台》为例进行选题答辩实录分享
python·django