一、Simple‑JWT介绍
- 旧库:
django‑rest‑framework‑jwt(不再维护) - 新库: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. 流程
- 调用
/login/传用户名密码,返回access、refresh - 业务接口请求头带上
Bearer access_token访问 - access过期,调用
/refresh/提交refresh,拿到全新access - 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,...}。
步骤
- 继承
TokenObtainPairSerializer重写validate() - 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编码,不是加密,不要存敏感信息!
六、多方式登录(用户名 / 手机号 / 密码登录)
需求:前端同一个字段,既可以填用户名、手机号、邮箱,配合密码登录。
关键点
- 自定义用户模型必须在第一次migrate之前定义,一旦迁移完成再改用户表非常麻烦。
# settings.py
AUTH_USER_MODEL = "app01.UserInfo"
- 签发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,不重复造轮子。
面试重点
- access短有效期目的:即使token泄露,很快自动失效,降低风险。
- refresh只用来换新access,不能直接访问业务接口。
- refresh_token可以存在数据库,实现主动踢用户下线;原生JWT本身无法主动失效。
- payload里面的数据只是base64编码,不是加密,不能存放密码、敏感数据。
- simple‑jwt如果不写权限类
IsAuthenticated,不带token请求不会拦截。
如果你需要,我可以再帮你整理一份手写JWT vs simple‑jwt的思维导图版笔记。