DRF学习08-SimpleJWT 笔记

一、Simple‑JWT介绍

  1. 旧库:django‑rest‑framework‑jwt(不再维护)
  2. 新库:djangorestframework‑simplejwt(企业主流,持续维护)

底层实现双token:access短期令牌、refresh长期刷新令牌

  • access_token:有效期短,访问业务接口
  • refresh_token:有效期长,专门用来换取新access,用户不用重复输密码

安装

pip install djangorestframework-simplejwt

底层默认

默认使用Django内置auth_user用户表做签发与校验。


二、快速上手(使用Django内置User表)

1. 路由配置

from rest_framework_simplejwt.views import TokenObtainPairView,TokenRefreshView

urlpatterns = [
    # 登录:签发 access + refresh
    path('login/', TokenObtainPairView.as_view()),
    # 刷新token:拿refresh换新access
    path('refresh/', TokenRefreshView.as_view()),
]

2. 视图使用认证

from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework_simplejwt.authentication import JWTTokenUserAuthentication

class BookView(APIView):
    authentication_classes = [JWTTokenUserAuthentication]
    permission_classes = [IsAuthenticated]

    def get(self,request):
        return Response({"msg":"需要登录才能访问","user":request.user.username})

⚠️注意坑:
如果只写authentication_classes,不写IsAuthenticated:

  • 带错误token → 直接抛401报错
  • 不带token,不会执行校验,直接放行接口

3. 请求头格式

Authorization: Bearer 三段式JWT字符串

业务接口请求携带的是 access_token,refresh只用于刷新接口。

4. 流程

  1. 调用/login/传用户名密码,返回access、refresh
  2. 业务接口请求头带上Bearer access_token访问
  3. access过期,调用/refresh/提交refresh,拿到全新access
  4. refresh过期,需要重新登录

三、settings配置 SIMPLE_JWT

from datetime import timedelta

SIMPLE_JWT = {
    "ACCESS_TOKEN_LIFETIME": timedelta(minutes=5),   # access有效期
    "REFRESH_TOKEN_LIFETIME": timedelta(days=7),     # refresh有效期

    "ROTATE_REFRESH_TOKENS": False,       # 是否刷新时生成新refresh
    "BLACKLIST_AFTER_ROTATION": False,    # 旧refresh是否加入黑名单
    # 开启黑名单需要注册app:rest_framework_simplejwt.token_blacklist

    "ALGORITHM": "HS256",
    "SIGNING_KEY": settings.SECRET_KEY,   # 签名密钥,复用Django密钥

    "AUTH_HEADER_TYPES": ("Bearer",),     # 请求头前缀 Bearer
    "AUTH_HEADER_NAME": "HTTP_AUTHORIZATION",

    "USER_ID_FIELD": "id",      # 用户模型哪个字段作为id
    "USER_ID_CLAIM": "user_id", # jwt payload中存储用户id的key

    # 可以替换自定义序列化器
    "TOKEN_OBTAIN_SERIALIZER": "app01.serializers.MyTokenObtainPairSerializer",
    "TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSerializer",
}

四、定制登录返回格式

simple‑jwt原生登录返回 {"access":"xxx","refresh":"xxx"},需要改成我们统一格式 {code,msg,...}。

步骤

  1. 继承TokenObtainPairSerializer重写validate()
  2. settings中SIMPLE_JWT配置TOKEN_OBTAIN_SERIALIZER指向该类
# serializers.py
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer

class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
    def validate(self, attrs):
        # 父类做账号密码校验,生成access、refresh
        data = super().validate(attrs)
        user = self.user
        return {
            "code":100,
            "msg":"登录成功",
            "username":user.username,
            "access":data["access"],
            "refresh":data["refresh"]
        }

settings.py

SIMPLE_JWT = {
    "TOKEN_OBTAIN_SERIALIZER": "app01.serializers.MyTokenObtainPairSerializer"
}

五、修改JWT的payload载荷(往token里面存自定义数据)

重写类方法get_token

class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
    @classmethod
    def get_token(cls, user):
        token = super().get_token(user)
        # 往payload增加自定义字段
        token["username"] = user.username
        return token

    def validate(self, attrs):
        data = super().validate(attrs)
        return {"code":100,"msg":"登录成功",**data}

注意:payload只是base64编码,不是加密,不要存敏感信息!


六、多方式登录(用户名 / 手机号 / 密码登录)

需求:前端同一个字段,既可以填用户名、手机号、邮箱,配合密码登录。

关键点

  1. 自定义用户模型必须在第一次migrate之前定义,一旦迁移完成再改用户表非常麻烦。
# settings.py
AUTH_USER_MODEL = "app01.UserInfo"
  1. 签发token核心代码(不需要用simple‑jwt自带视图)
from rest_framework_simplejwt.tokens import RefreshToken

# 根据user对象直接生成双token
refresh = RefreshToken.for_user(user)
refresh_str = str(refresh)
access_str = str(refresh.access_token)

方案:把校验写在序列化器全局钩子validate中

# serializers.py
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
import re
from .models import UserInfo
from rest_framework_simplejwt.tokens import RefreshToken

class LoginSerializer(serializers.Serializer):
    username = serializers.CharField()
    password = serializers.CharField()

    def validate(self, attrs):
        username = attrs["username"]
        password = attrs["password"]
        # 判断是手机号 /邮箱 /普通用户名
        if re.match(r'^1[3-9]\d{9}$',username):
            user = UserInfo.objects.filter(mobile=username).first()
        elif re.search(r"@",username):
            user = UserInfo.objects.filter(email=username).first()
        else:
            user = UserInfo.objects.filter(username=username).first()

        # 校验密码
        if user and user.check_password(password):
            refresh = RefreshToken.for_user(user)
            # 通过context把token传给视图
            self.context["refresh"] = str(refresh)
            self.context["access"] = str(refresh.access_token)
            return attrs
        else:
            raise ValidationError("用户名或密码错误")

视图:

class MultiLoginView(APIView):
    authentication_classes = []
    permission_classes = []
    def post(self,request):
        ser = LoginSerializer(data=request.data,context={})
        ser.is_valid(raise_exception=True)
        return Response({
            "code":100,
            "msg":"登录成功",
            "access":ser.context["access"],
            "refresh":ser.context["refresh"]
        })

知识点:serializer.context字典,视图和序列化器之间传递数据。


七、自定义用户表:手写签发 + 手写认证类

如果不用Django内置User模型,需要自己写认证逻辑。

签发token(视图+序列化器)

# 签发token核心
refresh = RefreshToken.for_user(user)
access_token = str(refresh.access_token)
refresh_token = str(refresh)

手写认证类(模仿simple‑jwt底层逻辑)

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_simplejwt.tokens import AccessToken
from rest_framework_simplejwt.exceptions import TokenError
from .models import User

class MyJwtAuth(BaseAuthentication):
    def authenticate(self, request):
        auth_header = request.META.get("HTTP_AUTHORIZATION")
        if not auth_header or not auth_header.startswith("Bearer "):
            raise AuthenticationFailed("token缺失或者格式错误")

        token_str = auth_header.split(" ")[1]
        try:
            # simple‑jwt帮我们做签名、过期校验
            token_obj = AccessToken(token_str)
            user_id = token_obj["user_id"]
            user = User.objects.filter(pk=user_id).first()
            if not user:
                raise AuthenticationFailed("用户不存在")
            return (user,token_str)
        except TokenError as e:
            raise AuthenticationFailed(str(e))

视图使用:

class BookView(APIView):
    authentication_classes = [MyJwtAuth]
    permission_classes = [IsAuthenticated]

对比:手写JWT VS Simple‑JWT

项目 手写JWT(我们之前写的) simple‑jwt第三方库
签名、过期校验 全部自己编码实现 库内部封装完成
refresh刷新令牌 自己建表存uuid实现 原生支持双token
黑名单踢下线 自己做redis/数据库黑名单 自带黑名单app
payload自定义 自己编码 重写get_token方法即可
缺点 大量底层代码,需要自己处理各种异常 黑盒,定制需要阅读源码

学习阶段手写,真实企业开发优先simple‑jwt,不重复造轮子。


面试重点

  1. access短有效期目的:即使token泄露,很快自动失效,降低风险。
  2. refresh只用来换新access,不能直接访问业务接口。
  3. refresh_token可以存在数据库,实现主动踢用户下线;原生JWT本身无法主动失效。
  4. payload里面的数据只是base64编码,不是加密,不能存放密码、敏感数据。
  5. simple‑jwt如果不写权限类IsAuthenticated,不带token请求不会拦截。

如果你需要,我可以再帮你整理一份手写JWT vs simple‑jwt的思维导图版笔记。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容