08-接口文档和JWT
接口文档
楔子
接口文档对于协调前后端开发非常重要,可以避免因为开发习惯不同而导致的意外情况。在项目中,如果前后端开发各自为战,可能会出现不一致的情况。因此,接口文档可以约束双方,确保他们按照统一的规范进行开发,从而提高协同开发的效率和一致性。
规范
接口文档一般包括以下内容:
-
接口描述: 对每个接口进行描述,包括其功能、输入参数、输出格式和预期行为。
-
请求方法: 每个接口支持的请求方法,例如 GET、POST、PUT、DELETE 等。
-
请求参数: 每个接口所需的请求参数,包括参数名、类型、是否必需等信息。
-
响应格式: 每个接口的响应格式,包括响应数据的结构、类型等信息。
-
错误处理: 定义了接口可能返回的错误状态码和对应的错误信息,以及客户端应该如何处理这些错误。
-
示例: 可能包括一些请求和响应的示例,以便开发人员更好地理解接口的使用方式。
-
安全要求: 如果接口需要进行身份验证或授权,接口文档应包括相应的安全要求和机制。
-
版本控制: 如果接口存在多个版本,接口文档应该明确标明每个版本的区别和兼容性信息。
接口文档展现形式
- word形式
- markdown形式
- json和yaml
- 公司自研
- 开源的接口文档平台
- 比如Yapi 百度开源的
- 通过项目,自动生成接口文档平台
- coreapi
- drf-yasg
- 接口文档命名,建议包含版本字样,比如:书籍请求接口-v1.md
一个简单的接口文档实例
# 1. 用户相关
## 1.1 登录接口
# 请求地址
# 请求参数
# 编码格式
# 响应字段详解
## 1.2 注册接口
# 请求地址
# 请求参数
# 编码格式
# 响应字段详解
# ...

自动生成接口文档 coreapi (了解)
安装
pip install coreapi
使用
# 路由层
from rest_framework.documentation import include_docs_urls
urlpatterns = [
path("docs/", include_docs_urls(title="书籍查询接口文档"))
]
# 视图层
from rest_framework.viewsets import ModelViewSet
from .serializer import BookSerializer
from .models import Book
class BookViewSet(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
如果遇到报错
#AttributeError: 'AutoSchema' object has no attribute 'get_link'
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
# 新版drf schema_class默认用的是rest_framework.schemas.openapi.AutoSchema
}
启动项目:http://127.0.0.1:8000/app01/docs/

说明
- 模型层种的 help_text 控制描述文本,即上图种的出版社
- 因为序列化层和模型层的字段基本一样,所以序列化层设置一样的,二选一即可。
- 必填和不必填,取决于序列化类中的required参数默认为True,不必填写成False即可。
- 方法下的3引号注释,会生成介绍,response的文本会返回结果
def list(self, request):
"""
查询全部书籍的接口
"""
return Response("接口描述")

jwt
详细介绍:https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案。
使用jwt 必须使用django自带的user表。
并不限制语言,所有的web框架都可以采用这种认证方式。
三段式,用 . 去分割 每一段使用base64编码 (base64不是加密方案,只是编码方案)
- 头 一般放:公司信息,加密方式,是jwt,一般都是固定的
- header
- 荷载 一般放:登录用户的信息:用户id,过期时间,签发时间,是否是超级用户。
不能放用户密码!,能加能删,一般会往里面加东西,而不会删东西。
- payload
- 签名 signature 二进制数据 校验阶段主要是第三段。
- 签发阶段:通过头和荷载 使用 某种加密方式[HS256,md5,sha1等]加密得到。
- 校验阶段:拿到token,取出第一和第二部分,通过同样的加密方式获得新前面,用新的签名对比第三段(老签名)比较,如果一样,说明数据没有被修改,信任,正常处理。如果不一样,说明数据被篡改或者是模拟生成的,不能信任,返回错误。
- 注:这个生成的token 浏览器可以截获,不修改,然后模拟发送请求,不能避免。
jwt实现了,数据不在后端,但是比cookie更安全。
access token默认只有5分钟有效。浏览器大部分可以看到荷载,但是看不到第一阶段的数据(盐)
django中的盐默认在setting中
SECRET_KEY = "django-insecure-b_$m3)zmrq5sy&j_4l&qwylk%&-bkn5k1!-a^!j_#*-!gw37!1"
-是一种前后端登陆认证的方案,区别于之前的 cookie,session
-它有 签发阶段--》登陆成功后签发
-它有 认证阶段--》需要登陆后才能访问的接口,通过认证后,才能继续操作
三段式 使用 . 去分割
这三段分别代表什么?
# 下面这个就是jwt后的token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
base64编码
用途:
- 网络中传输
- 图片传输
- jwt使用
如何使用:
- 编码:
- 文字或者图片编码成二进制
- 使用base64.继续编码即可
- 解码:
- 使用base64.b64decode(二进制即可)
- 如果是文本,再转成文本即可。
base64编码一长度一定是4的倍数,如果不是用=号补齐,正常情况下=号最多3为,如果解不出可以尝试加=号,如果已经满足了,后面可以写N个等号。
编码文本
In [2]: import base64
In [3]: name = '小满'
In [4]: base64.b64encode(name.encode("utf-8"))
Out[4]: b'5bCP5ruh'
In [5]: base64.b64decode(b'5bCP5ruh').decode()
Out[5]: '小满'
编码图片
import base64
# 输入图片文件路径
input_path = r"C:\小满\Pictures\best.jpg"
# 输出的 base64 编码后的文本文件路径
output_path = "./结果.txt"
# 将图片文件编码为 base64 并写入到文本文件中
with open(input_path, "rb") as f1, open(output_path, "wb") as f2:
while data := f1.read():
# 编码为 base64
encoded_data = base64.b64encode(data)
# 将编码后的数据写入文本文件
f2.write(encoded_data)
# 从 base64 编码的文本文件解码并写入到新的图片文件中
with open(output_path, "rb") as f1, open("./小满.jpg", "wb") as f2:
while data := f1.read():
# 解码 base64 数据
decoded_data = base64.b64decode(data)
# 将解码后的数据写入新的图片文件
f2.write(decoded_data)
jwt开发重点
-签发token---》登陆接口
-校验token---》认证类
simplejwt
如何使用
- 登陆签发:默认使用auth的user表
- 只是测验的话,默认不需要视图层也可以测验。
# 自己写:登录 认证
# 第三方:方便快捷
# python的django框架中使用jwt
- 安装
- pip install djangorestframework-simplejwt
# 路由层
from rest_framework_simplejwt.views import token_obtain_pair
urlpatterns = [
path("login/", token_obtain_pair) # http:127.0.0.1:8000/login/ --> post请求 即可
]
# 视图层
from rest_framework.views import APIView
from rest_framework.mixins import CreateModelMixin
# 测试jwt
class LoginView(APIView, CreateModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
必须登录后才能发送post 比如新增一本图书 其他的视图(路由按上面的配置即可)
from rest_framework.permissions import IsAuthenticated # 第一个模块
from rest_framework_simplejwt.authentication import JWTAuthentication # 第二个模块
# 书籍 测试接口工具
# 127.0.0.1:8000/app01/book/
class BookViewSet(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 必须登录后才能新增
authentication_classes = [JWTAuthentication] # 第一位
permission_classes = [IsAuthenticated] # 第二位

指定请求头
加了验证正常新增成功 注意 这里的key必须是 Authorization value必须加上 Bearer (可修改)的前缀


# refresh 刷新时间
# access 真正使用的
# refresh的过期时间更长一些,默认是7天
# access 过期时间更短一些,默认是1天
# 后续可以通过 refresh 去 更新 access 让用户无感刷新
{
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTcxMzU3NzY0NywiaWF0IjoxNzEzNDkxMjQ3LCJqdGkiOiJiYzcwYjlkNGFiODM0NzdkOWIwOTEwMmQ0YmQ1OTNhYyIsInVzZXJfaWQiOjF9.GKGedTNKaFfnj_vJspKYrMOOf-29E0qYy_itErJAWPE",
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzEzNDkxNTQ3LCJpYXQiOjE3MTM0OTEyNDcsImp0aSI6IjQ4OWUyMTU3ZWY1MTQ5ZGNiOGEyOTI1M2Y5OTA0ZmZkIiwidXNlcl9pZCI6MX0.Uo16D_HHKQTkTGymQcNsdig3kFknr8D2Ox52vmtqUyQ"
}
JWT国际化
同drf一样,记得注册app,app的名字是
JWT的默认配置
# 通常情况下,只需要修改几个配置即可,把下面的配置,直接复制到settings里面
from datetime import timedelta
SIMPLE_JWT = {
# Access Token的有效期
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
# Refresh Token的有效期
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
# 用于指定JWT在HTTP请求头中使用的身份验证方案。默认为"Bearer"
"AUTH_HEADER_TYPES": ("Token",),
# 用于生成访问令牌和刷新令牌的序列化器。 改成你自己的即可
"TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer",
}
下面是默认配置
# JWT配置
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5), # Access Token的有效期
'REFRESH_TOKEN_LIFETIME': timedelta(days=7), # Refresh Token的有效期
# 对于大部分情况,设置以上两项就可以了,以下为默认配置项目,可根据需要进行调整
# 是否自动刷新Refresh Token
'ROTATE_REFRESH_TOKENS': False,
# 刷新Refresh Token时是否将旧Token加入黑名单,如果设置为False,则旧的刷新令牌仍然可以用于获取新的访问令牌。需要将'rest_framework_simplejwt.token_blacklist'加入到'INSTALLED_APPS'的配置中
'BLACKLIST_AFTER_ROTATION': False,
'ALGORITHM': 'HS256', # 加密算法
'SIGNING_KEY': settings.SECRET_KEY, # 签名密匙,这里使用Django的SECRET_KEY
# 如为True,则在每次使用访问令牌进行身份验证时,更新用户最后登录时间
"UPDATE_LAST_LOGIN": False,
# 用于验证JWT签名的密钥返回的内容。可以是字符串形式的密钥,也可以是一个字典。
"VERIFYING_KEY": "",
"AUDIENCE": None,# JWT中的"Audience"声明,用于指定该JWT的预期接收者。
"ISSUER": None, # JWT中的"Issuer"声明,用于指定该JWT的发行者。
"JSON_ENCODER": None, # 用于序列化JWT负载的JSON编码器。默认为Django的JSON编码器。
"JWK_URL": None, # 包含公钥的URL,用于验证JWT签名。
"LEEWAY": 0, # 允许的时钟偏差量,以秒为单位。用于在验证JWT的过期时间和生效时间时考虑时钟偏差。
# 用于指定JWT在HTTP请求头中使用的身份验证方案。默认为"Bearer"
"AUTH_HEADER_TYPES": ("Bearer",),
# 包含JWT的HTTP请求头的名称。默认为"HTTP_AUTHORIZATION"
"AUTH_HEADER_NAME": "HTTP_AUTHORIZATION",
# 用户模型中用作用户ID的字段。默认为"id"。
"USER_ID_FIELD": "id",
# JWT负载中包含用户ID的声明。默认为"user_id"。
"USER_ID_CLAIM": "user_id",
# 用于指定用户身份验证规则的函数或方法。默认使用Django的默认身份验证方法进行身份验证。
"USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule",
# 用于指定可以使用的令牌类。默认为"rest_framework_simplejwt.tokens.AccessToken"。
"AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",),
# JWT负载中包含令牌类型的声明。默认为"token_type"。
"TOKEN_TYPE_CLAIM": "token_type",
# 用于指定可以使用的用户模型类。默认为"rest_framework_simplejwt.models.TokenUser"。
"TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser",
# JWT负载中包含JWT ID的声明。默认为"jti"。
"JTI_CLAIM": "jti",
# 在使用滑动令牌时,JWT负载中包含刷新令牌过期时间的声明。默认为"refresh_exp"。
"SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp",
# 滑动令牌的生命周期。默认为5分钟。
"SLIDING_TOKEN_LIFETIME": timedelta(minutes=5),
# 滑动令牌可以用于刷新的时间段。默认为1天。
"SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=1),
# 用于生成访问令牌和刷新令牌的序列化器。
"TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer",
# 用于刷新访问令牌的序列化器。默认
"TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSerializer",
# 用于验证令牌的序列化器。
"TOKEN_VERIFY_SERIALIZER": "rest_framework_simplejwt.serializers.TokenVerifySerializer",
# 用于列出或撤销已失效JWT的序列化器。
"TOKEN_BLACKLIST_SERIALIZER": "rest_framework_simplejwt.serializers.TokenBlacklistSerializer",
# 用于生成滑动令牌的序列化器。
"SLIDING_TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainSlidingSerializer",
# 用于刷新滑动令牌的序列化器。
"SLIDING_TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer",
}
定制返回格式
默认的格式

指定返回默认格式
# 1. 新建一个序列化类,比如 serializer
# 2. 在serializer中
# 导入模块
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
# 写一个类 继承 TokenObtainPairSerializer
class JWTSerializer(TokenObtainPairSerializer):
def validate(self, attrs):
dic = super().validate(attrs) # 这里取到的就是一个字典 包含refresh和access
data = {
"code": 100,
"msg": "登录成功",
"username": self.user.username, # 这个就是用户对象
"refresh": dic.get("refresh"),
"access": dic.get("access")
}
# 定制号直接返回,因为源码中帮我们处理过异常了,所以不需要再次处理
return data
# 3. 在settings中,配置成我们自己的就行
SIMPLE_JWT = {
# 用于生成访问令牌和刷新令牌的序列化器。 改成你自己的即可
"TOKEN_OBTAIN_SERIALIZER": "app01.lib.serializer.JWTSerializer",
}
# 路由层
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from rest_framework_simplejwt.views import token_obtain_pair
urlpatterns = [
path("login/", token_obtain_pair)
]
# 前端直接访问 http://127.0.0.1:8000/app01/login/

定制payload格式
荷载中包含用户名的情况比较少,不过这个案例中尝试在荷载中加一个用户名。
# 步骤和指定返回格式一模一样,只是多了一个东西
# 重写get_token即可
# 定制返回格式
class JWTSerializer(TokenObtainPairSerializer):
@classmethod
def get_token(cls, user):
username = user.username
# 对象调用类的绑定方法,会自动把对象的类传入
# token对象,当字典用 看源码
token = super().get_token(user)
token['username'] = username
return token
def validate(self, attrs):
dic = super().validate(attrs)
data = {
"code": 100,
"msg": "登录成功",
"username": self.user.username, # 这个就是用户对象
"refresh": dic.get("refresh"),
"access": dic.get("access")
}
# 定制号直接返回,因为源码中帮我们处理过异常了,所以不需要再次处理
return data
# 之前的
{"token_type":"access","exp":1713522991,"iat":1713522691,"jti":"63dbcb48fadb480886cec39010a69e4c","user_id":1}
# 我们添加用户名的
{"token_type":"access","exp":1713524715,"iat":1713524415,"jti":"16c31123e07940fb85c8bcb0c382a52c","user_id":1,"username":"admin"}
多方式登录(基于auth的user表)
- 默认的auth的user表,只能传用户名,密码校验
- 但是项目中,一般是 手机号/用户名/邮箱 + 密码 的方式去登录。
- 这个时候,使用simple_jwt就不行了,因为它只能校验用户名和密码
- 所以需要扩写user表,实现多方式登录。-->还是使用auth的user表,增加字段。
- 签发token:我们自己签发
- 认证:继续使用simple_jwt的认证即可
- 编写一个多方式登录接口
- 扩写auth的user表-->加入mobile字段
- 坑:
- 之前迁移过-->auth的user表已经生成了,就不能扩写了
- 方案1,创建新项目,从头做
- 删库,删除迁移记录(咱们自己app和django自己的迁移记录)删除auth和amdin的迁移记录自己的需要去源码里面删除。
- 所以,如果要扩写auth的user表,必须在迁移之前就定好,写好。


# 视图层
# 在此视图上增加mobile,登录成功我们签发token,simple_jwt帮我们做验证
class JWTView(GenericViewSet):
# 因为我们这里只是做反序列化,不会给前端表中的书籍,所以这里不写都可以
queryset = None #
serializer_class = LoginSerializer
@action(methods=["POST"], detail=False)
def login(self, request):
# 正常逻辑: 取出手机号/邮箱/用户名 去数据库进行校验, 校验通过 签发token 返回给前端
# 高级逻辑: 上述逻辑去序列化类做 --> 更精简
serializer = self.get_serializer(data=request.data)
if serializer.is_valid(): # 执行三层认证 自己的 局部钩子 全局钩子
# 校验通过,返回给前端
# 现在在视图类中 有一个序列化类 ——-》如果要把序列化类中的变量给视图,或者视图类中的变量给序列化类,需要借助——-》context
# context 就是一个桥梁 [字典]
# 如果要把request传递过去,上面写context={"request": request}即可
refresh = serializer.context.get("refresh")
access = serializer.context.get("access")
token = serializer.context.get("token")
return Response({"code": 100, "msg": "登录成功", "refresh": refresh, "access": access}, headers={"token": token})
else:
return Response({"code": 101, "msg": serializer.errors})
# 序列化层
import re
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework.exceptions import ValidationError
from app01.models import Book, UserInfo
# 登录序列化类校验
class LoginSerializer(TokenObtainPairSerializer):
username = serializers.CharField() # 可能是用户名 邮箱 手机号
password = serializers.CharField()
# 逻辑封装到函数里面
def _get_user(self, attrs):
# 获取用户名和密码
username = attrs.get("username", None)
password = attrs.get("password", None)
# 去数据库查询 username可能是手机号 邮箱 用户名 需要通过邮箱去匹配
if re.match(r"^1[3-9]\d{9}$", username):
user = UserInfo.objects.filter(mobile=username).first()
elif re.match(r".*?@.*[com|cn|net]", username):
user = UserInfo.objects.filter(email=username).first()
else:
user = UserInfo.objects.filter(username=username).first()
if user and user.check_password(password):
return user
else:
raise ValidationError("用户名或密码错误")
# 从函数取出校验后的书籍,让代码更精简
def validate(self, attrs):
user = self._get_user(attrs)
# 签发token 通过user去签发
# 这里直接拿到的token是refersh的
token = RefreshToken.for_user(user)
self.context['refresh'] = str(token)
self.context['access'] = str(token.access_token)
self.context['token'] = str(token)
return attrs
# 路由
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from rest_framework_simplejwt.views import token_obtain_pair
from .views import JWTView
router = DefaultRouter()
router.register("jwt", JWTView, basename="jwt")
urlpatterns = [
path("", include(router.urls)),
path("login/", token_obtain_pair) # http://127.0.0.1:8000/app01/jwt/login/
]
总结
# 1 校验数据,放到序列化类的 validate中,而不放在视图类的方法中乐
# 2 视图类和序列化类直接交互变量
serializer.context
# 3 user.check_password 必须是auth的user表,校验密码使用它
# 4 attrs必须返回值,返回空报错
# 5 视图类的方法校验失败的else中:也要return Response
# 6 如何签发token
token = RefreshToken.for_user(user)
self.context['access'] = str(token.access_token)
self.context['refresh'] = str(token)
自定义用户表,手动签发和认证
# 视图层
class JWTOurView(GenericViewSet):
serializer_class = LoginOurSerializer
@action(methods=["POST"], detail=False)
def login(self, request):
# 正常逻辑: 取出手机号/邮箱/用户名 去数据库进行校验, 校验通过 签发token 返回给前端
# 高级逻辑: 上述逻辑去序列化类做
serializer = self.get_serializer(data=request.data)
if serializer.is_valid(): # 执行三层认证 自己的 局部钩子 全局钩子
refresh = serializer.context.get("refresh")
access = serializer.context.get("access")
token = serializer.context.get("token")
return Response({"code": 100, "msg": "登录成功", "refresh": refresh, "access": access}, headers={"token": token})
else:
return Response({"code": 101, "msg": serializer.errors})
# 序列化类
from rest_framework import serializers
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework.exceptions import ValidationError
from app01.models import User
from rest_framework_simplejwt.tokens import RefreshToken
class LoginOurSerializer(TokenObtainPairSerializer):
username = serializers.CharField()
password = serializers.CharField()
# 逻辑全部写这里
def _get_user(self, attrs):
# 获取用户名和密码
username = attrs.get("username", None)
password = attrs.get("password", None)
# 去数据库查询 username可能是手机号 邮箱 用户名 需要通过邮箱去匹配
if re.match(r"^1[3-9]\d{9}$", username):
user = UserInfo.objects.filter(mobile=username).first()
elif re.match(r".*?@.*[com|cn|net]", username):
user = UserInfo.objects.filter(email=username).first()
else:
user = UserInfo.objects.filter(username=username).first()
if user and user.check_password(password):
return user
else:
raise ValidationError("用户名或密码错误")
# 这里都不需要修改
def validate(self, attrs):
user = self._get_user(attrs)
token = RefreshToken.for_user(user)
self.context['refresh'] = str(token)
self.context['access'] = str(token.access_token)
self.context['token'] = str(token)
return attrs
自定义用户认证类
# 模型层
class Book(models.Model):
name = models.CharField(max_length=32, verbose_name="图书名称")
price = models.IntegerField(verbose_name="图书价格")
publish = models.CharField(max_length=32, verbose_name="出版社")
class Meta:
db_table = "book"
# 自定义认证类
from rest_framework_simplejwt.authentication import JWTAuthentication
from app01.models import User
from rest_framework.exceptions import AuthenticationFailed
class JWTBaseAuthentication(JWTAuthentication):
def authenticate(self, request):
token = request.META.get("HTTP_AUTHORIZATION")
if token:
# 这里返回的是payload数据,是可以信任的payload,所以可以从这里直接拿取到用户的id user_id
get_validated_token = self.get_validated_token(token)
user_id = get_validated_token['user_id']
user = User.objects.get(pk=user_id)
return user, token
else:
raise AuthenticationFailed("请携带登录信息")
# 视图
from rest_framework.viewsets import ModelViewSet
from app01.lib.authentication import JWTBaseAuthentication
class BookView(ModelViewSet):
# 不登录无法操作
authentication_classes = [JWTBaseAuthentication]
queryset = Book.objects.all()
serializer_class = serializer.BookSerializer



浙公网安备 33010602011771号