drf接口文档,jwt介绍和原理,drf_jwt快速使用,定制返回格式,jwt的认证类
内容回顾
认证类的请求执行流程—》源码分析
请求进来》路由匹配》path('test/', view.BookView.as_view()),》继承了APIView》APIView中的as_view()内部的闭包函数view》这个view中执行了self.dispatch()》APIView中的dispatch()》
def dispatch(self, request, *args, **kwargs):
# 包装了新的request
request = self.initialize_request(request, *args, **kwargs)
。。。
# 执行3大认证
self.initial(request, *args, **kwargs)
# 下面执行视图类的方法
。。。。。。。
在APIView中的initial()方法中
def initial(self, request, *args, **kwargs):
# 认证
self.perform_authentication(request)
# 权限
self.check_permissions(request)
# 频率
self.check_throttles(request)
在APIView中的perform_authentication()方法
def perform_authentication(self, request):
request.user
request是包装后的新的request
在restframework.request中Request类中
@property
def user(self):
if not hasattr(self, '_user'):
with wrap_attributeerrors(): # 上下文管理器----》面试
self._authenticate()
return self._user
在request中_authenticate()
def _authenticate(self):
# self.authenticators---》列表[认证类对象1,认证类对象2]
# authenticator 是认证类的对象
for authenticator in self.authenticators:
try:
#认证类对象.authenticate self 是新的request对象
user_auth_tuple = authenticator.authenticate(self)
except exceptions.APIException:
self._not_authenticated()
raise
if user_auth_tuple is not None:
self._authenticator = authenticator
#self 是新的request
# request.user 就是当前登录用户
# request.auth 一般把token给它
self.user, self.auth = user_auth_tuple
return
self._not_authenticated()
# self.authenticators:是什么时候传入的?执行__init__ 就是在Request初始化的时候传入的
在APIView的dispatch的
self.initialize_request(request, *args, **kwargs)初始化的,
return Request(
request,
parsers=self.get_parsers(),
#在这里传入的self.initialize_request(request, *args, **kwargs)初始化的,
return Request(
request,
parsers=self.get_parsers(),
#在这里传入的
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
APIView的get_authenticators()》
return [auth() for auth in self.authentication_classes]
self.authentication_classes:视图类中配置的一个个的认证类的列表,如果没配,配置文件中,内置配置文件中
权限类的执行流程
请求进来》路由匹配》path('test/', view.BookView.as_view()),》继承了APIView》APIView中的as_view()内部的闭包函数view》这个view中执行了self.dispatch()》APIView中的dispatch()》
def dispatch(self, request, *args, **kwargs):
# 包装了新的request
request = self.initialize_request(request, *args, **kwargs)
。。。
# 执行3大认证
self.initial(request, *args, **kwargs)
# 下面执行视图类的方法
。。。。。。。
在APIView中的initial()方法中
def initial(self, request, *args, **kwargs):
# 认证
self.perform_authentication(request)
# 权限
self.check_permissions(request)
# 频率
self.check_throttles(request)
在APIView中的check_permissions(request)
def check_permissions(self, request):
# 循环权限类对象,执行权限类的has_permission方法如果返回True说明有权限继续执行,如果返回False说明没权限,立即停止执行message是没权限是可以返回的自定义信息
for permission in self.get_permissions():
if not permission.has_permission(request, self):
self.permission_denied(
request,
message=getattr(permission, 'message', None),
code=getattr(permission, 'code', None)
)
频率类的执行流程
请求进来》路由匹配》path('test/', view.BookView.as_view()),》继承了APIView》APIView中的as_view()内部的闭包函数view》这个view中执行了self.dispatch()》APIView中的dispatch()》
def dispatch(self, request, *args, **kwargs):
# 包装了新的request
request = self.initialize_request(request, *args, **kwargs)
。。。
# 执行3大认证
self.initial(request, *args, **kwargs)
# 下面执行视图类的方法
。。。。。。。
在APIView中的initial()方法中
def initial(self, request, *args, **kwargs):
# 认证
self.perform_authentication(request)
# 权限
self.check_permissions(request)
# 频率
self.check_throttles(request)
在APIView中的check_throttles(request)
def check_throttles(self, request):
#for循环频率类对象,调用对象的allow_request方法,返回True说频率没超限制,返回False说明频率超过限制了,立即停止继续执行
throttle_durations = []
for throttle in self.get_throttles():
if not throttle.allow_request(request, self):
throttle_durations.append(throttle.wait())
# 如果超过限制这个列表就会有值
if throttle_durations:
# Filter out `None` values which may happen in case of config / rate
# changes, see #1438
durations = [
duration for duration in throttle_durations
if duration is not None
]
duration = max(durations, default=None)
# 这里会抛异常被全局异常捕获到
self.throttled(request, duration)
自己定义了一个频率类,基于BaseThrottle,重写allow_request
class SuperSimpleRateThrottle(SimpleRateThrottle):
# 可变类型,产生对象的时候,如果里面有内容不会清空
VISIT_RECORD = {}
def __init__(self):
self.history = None
def allow_request(self, request, view):
# 自己写逻辑,判断是否超频
# (1)取出访问者ip
# (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走 {ip地址:[时间1,时间2,时间3,时间4]}
# (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
# (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
# (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
# (1)取出访问者ip
ip = request.META.get('REMOTE_ADDR')
import time
ctime = time.time()
# (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问
if ip not in self.VISIT_RECORD:
self.VISIT_RECORD[ip] = [ctime, ]
return True
# self.history = [时间1]
self.history = self.VISIT_RECORD.get(ip, [])
# (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
while self.history and ctime - self.history[-1] > 60:
self.history.pop()
# (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
# (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
if len(self.history) < 3:
self.history.insert(0, ctime)
return True
else:
return False
SimpleRateThrottle
继承它,写代码少
只需要重写get_cache_key和scope类属性,配置文件配置
源码:allow_request》就是上面写的,可扩展性高,好多东西从配置文件取的
全局异常处理
源码中,在3大认证,视图类的方法中出错,就会执行:self.handle_exception(exc)
def handle_exception(self, exc):
# 去配置文件中找到:EXCEPTION_HANDLER对应的函数,exception_handler
exception_handler = self.get_exception_handler()
# exception_handler(exc,context)
response = exception_handler(exc, context)
return response
自己在配置文件中配置,以后出了异常,走咱们自己的有两个参数exc,content
exc,错误对象
content:上下文,包含View,request等等
今日内容
接口文档
前后端分离
我们做后端,写接口
前端做前端,根据接口写app,web,小程序
作为后端来讲,我们很清除,比如登录接口
/api/v1/login/---->post---->username,password 编码格式json》返回的格式 {code:100,msg:登录成功}
后端人员,接口写完,一定要写接口文档
接口文档如何编写
- 使用word,md编写接口文档
- 使用第三方平台,编写我们的接口文档>收费,不安全,可能会跑路
https://www.showdoc.com.cn/item/index - 公司自己使用第三方开源的搭建的》Yapi>
自己搭建https://zhuanlan.zhihu.com/p/366025001 - 使用drf编写的接口,可以自动生成接口文档
swagger---》drf-yasg---》官方推荐使用
coreapi----》我们使用
使用coreapi自动生成接口文档步骤
-
安装
pip install coreapi
-
配置路由
from rest_framework.documentation import include_docs_urls path('docs/', include_docs_urls(title='xx项目接口文档')),
-
视图类,方法上,写注释即可
在类的最顶上加上注释,类下的所有方法都有
class BookViwe(GenericViewSet, ListAPIView, CreateAPIView): """ 查询图书类 """
在类的方法上加注释,就只要某一个方法有,会把类的注释信息顶掉
def list(self, request, *args, **kwargs): """ 查询所有图书方法 :param request: :param args: :param kwargs: :return: 返回查询所有图书信息 """
在序列化类或表模型的字段上加help_text,required。。。优先显示序列化类中的help_text信息
id= serializers.IntegerField(help_text='书籍id') class Book(models.Model): name = models.CharField(max_length=32) price = models.IntegerField(help_text='书籍价格')
-
配置文件配置
REST_FRAMEWORK = { 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema', }
-
方法地址:http://127.0.0.1:8000/docs
总结,接口文档,需要有的东西
-
描述
-
地址
-
请求方式
-
请求编码格式
-
请求数据详解(必填,类型)
-
返回格式案例
-
返回数据字段解释
-
错误码
jwt解释和原理
cookie,session,token,发展史
JWT全称:Json web token (JWT) 就是web方向token的使用
JWT的构成 三部分,每部分用.
分隔
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
第一部分:header,头
声明类型,这里是JWT
声明加密的算法 通常直接使用HMAC SHA256
公司信息。。。
第二部分:payload,荷载
存放有效信息的地方
过期时间
签发时间
用户id
用户名字。。
第三部分:signature,签名
第一部分和第二部分通过密钥+加密方式(HMAC SHA256)得到
jwt开发重点
登录接口—》签发token
认证类–》jwt认证
base64编码和解码
import base64
import json
# dic={'user_id':1,'username':"lqz"}
#
# dic_str=json.dumps(dic)
#
# #把这个字符串使用base64编码
# res=base64.b64encode(dic_str.encode('utf-8'))
# print(res) #
# 注意:base64编码后,字符长度一定是4的倍数,如果不是,使用 = 补齐, = 不表示数据
# 解码
res=base64.b64decode('TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ=')
print(res)
base64应用场景
-
jwt 使用了base64
-
网络中传输数据,也会经常使用,base64编码
-
网络传输中,有的图片使用base64编码
'''
s=''
res=base64.b64decode(s)
with open('a.png','wb') as f:
f.write(res)
drf-jwt快速使用
django+drf平台开发jwt这套东西,有两个模块
djangorestframework-jwt–>虽然年久失修,官网不支持django4.x了但是其中底层实现原理都是一样的
djangorestframework-simplejwt---》公司用的多
自己封装jwt签发和认证
使用步骤
-
安装
-
快速签发token–>登录接口,路由配置
使用django,user表实现快速认证path('login/', obtain_jwt_token),
-
postman,测试
http://127.0.0.1:8000/login/发送post请求,携带username和password
认证成功返回token
定制返回格式
以后如果是基于auth的User标签token,就可以不自己写了,但是登录接口返回格式,只有token,不符合公司规范
使用步骤
-
写个函数jwt_response_payload_handler
def jwt_response_payload_handler(token, user=None, request=None): return { 'code': 100, 'msg': '登录成功', 'username': user.username, 'token': token }
-
配置项目配置
JWT_AUTH = { 'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.myjwt.jwt_response_payload_handler', }
-
使用postman测试,就看到我们自定义的返回了
jwt认证类
以后接口要登陆后才能访问使用
-
在视图类上加一个认证类,一个权限类class
from rest_framework_jwt.authentication import JSONWebTokenAuthentication class BookViewDetail(GenericViewSet, RetrieveAPIView): # permission_classes = [CommonPermission, ] throttle_classes = [Commonthrottling, ] # 可以单独使用,单独使用时要以ip为唯一字段 queryset = Book.objects.all() serializer_class = BookSerializer authentication_classes = [JSONWebTokenAuthentication]
-
postman测试
请求头中key值叫Authorization
请求头的value值是:jwt有效的token
用户登录有权限,不登录没有权限
simplejwt
from rest_framework_simplejwt.views import token_obtain_sliding
path('login/', token_obtain_sliding),
settings.py
认证生成token的属性不同,解析的时候也是不同的,所以需要配置相同的认证token属性的类,其实就是对象加密属性
# simplejwt的加密属性
SIMPLE_JWT={
"AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.SlidingToken",), # 这个需要跟你路由中使用的生成类是一个
}
# 这个是定制值的第一个字符串
"AUTH_HEADER_TYPES": ("Bearer",),
# 这个是定制请求头字符串
"AUTH_HEADER_NAME": "HTTP_AUTHORIZATION",
view.py
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework.permissions import IsAuthenticated
class BookViewDetail(GenericViewSet, RetrieveAPIView):
# permission_classes = [CommonPermission, ]
throttle_classes = [Commonthrottling, ] # 可以单独使用,单独使用时要以ip为唯一字段
queryset = Book.objects.all()
serializer_class = BookSerializer
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated, ]
postman
header面key为AUTHORIZATION
values值为Bearer token
认证token类
class AccessToken(Token):
# token的属性
token_type = "access"
# 有效时间
lifetime = api_settings.ACCESS_TOKEN_LIFETIME
class RefreshToken(BlacklistMixin, Token):
token_type = "refresh"
lifetime = api_settings.REFRESH_TOKEN_LIFETIME
class UntypedToken(Token):
token_type = "untyped"
lifetime = timedelta(seconds=0)
class SlidingToken(BlacklistMixin, Token):
token_type = "sliding"
lifetime = api_settings.SLIDING_TOKEN_LIFETIME
自定义返回数据
继承要使用的view类,然后重写,然后再里面加就行了,因为目前暂时没发现可以再配置中替换返回数据的,所以暂时就用这个
from rest_framework_simplejwt.views import TokenObtainSlidingView
from rest_framework_simplejwt.exceptions import InvalidToken, TokenError
from rest_framework.response import Response
class MySlidingView(TokenObtainSlidingView):
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
try:
serializer.is_valid(raise_exception=True)
except TokenError as e:
raise InvalidToken(e.args[0])
# 这个就是我自己写的,上面的都是copy的原来的post的写法
res = {
'code': 100,
'msg': '登录成功',
'token': serializer.validated_data.get('token')
}
return Response(res, status=200)
simplejwt,序列化类的token属性
class TokenObtainSerializer(serializers.Serializer):
username_field = get_user_model().USERNAME_FIELD
# token属性
token_class = None
class TokenObtainPairSerializer(TokenObtainSerializer):
token_class = RefreshToken # 这里对应上面的
class TokenObtainSlidingSerializer(TokenObtainSerializer):
token_class = SlidingToken
class TokenRefreshSerializer(serializers.Serializer):
refresh = serializers.CharField()
access = serializers.CharField(read_only=True)
token_class = RefreshToken
class TokenRefreshSlidingSerializer(serializers.Serializer):
token = serializers.CharField()
token_class = SlidingToken
class TokenVerifySerializer(serializers.Serializer):
# token属性
token = serializers.CharField()
class TokenBlacklistSerializer(serializers.Serializer):
refresh = serializers.CharField()
# token属性
token_class = RefreshToken