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:登录成功}
后端人员,接口写完,一定要写接口文档

接口文档如何编写

  1. 使用word,md编写接口文档
  2. 使用第三方平台,编写我们的接口文档>收费,不安全,可能会跑路
    https://www.showdoc.com.cn/item/index
  3. 公司自己使用第三方开源的搭建的》Yapi>
    自己搭建https://zhuanlan.zhihu.com/p/366025001
  4. 使用drf编写的接口,可以自动生成接口文档
    swagger---》drf-yasg---》官方推荐使用
    coreapi----》我们使用

使用coreapi自动生成接口文档步骤

  1. 安装

    pip install coreapi

  2. 配置路由
    from rest_framework.documentation import include_docs_urls
            path('docs/', include_docs_urls(title='xx项目接口文档')),
    
  3. 视图类,方法上,写注释即可
    在类的最顶上加上注释,类下的所有方法都有
    class BookViwe(GenericViewSet, ListAPIView, CreateAPIView):
        """
        查询图书类
        """
    

    image-20230209165454146

    在类的方法上加注释,就只要某一个方法有,会把类的注释信息顶掉
    def list(self, request, *args, **kwargs):
        """
            查询所有图书方法
            :param request:
            :param args:
            :param kwargs:
            :return: 返回查询所有图书信息
            """
    

    image-20230209165539593

    在序列化类或表模型的字段上加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='书籍价格')
    

    image-20230209165631340

  4. 配置文件配置
    REST_FRAMEWORK = {
         		'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
    
        	}
    
  5. 方法地址:http://127.0.0.1:8000/docs
总结,接口文档,需要有的东西
  1. 描述
  2. 地址
  3. 请求方式
  4. 请求编码格式
  5. 请求数据详解(必填,类型)
  6. 返回格式案例
  7. 返回数据字段解释
  8. 错误码

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应用场景

  1. jwt 使用了base64
  2. 网络中传输数据,也会经常使用,base64编码
  3. 网络传输中,有的图片使用base64编码
'''
s=''
res=base64.b64decode(s)
with open('a.png','wb') as f:
    f.write(res)

image-20230209175903228

drf-jwt快速使用

django+drf平台开发jwt这套东西,有两个模块

djangorestframework-jwt–>虽然年久失修,官网不支持django4.x了但是其中底层实现原理都是一样的
djangorestframework-simplejwt---》公司用的多

自己封装jwt签发和认证

使用步骤

  1. 安装

  2. 快速签发token–>登录接口,路由配置
    使用django,user表实现快速认证

    path('login/', obtain_jwt_token),
    
  3. postman,测试

    http://127.0.0.1:8000/login/发送post请求,携带username和password
    

    image-20230209183259745

    认证成功返回token

    image-20230209183643750

定制返回格式

以后如果是基于auth的User标签token,就可以不自己写了,但是登录接口返回格式,只有token,不符合公司规范

使用步骤

  1. 写个函数jwt_response_payload_handler

    def jwt_response_payload_handler(token, user=None, request=None):
        return {
            'code': 100,
            'msg': '登录成功',
            'username': user.username,
            'token': token
        }
    
  2. 配置项目配置

    JWT_AUTH = {
    
        'JWT_RESPONSE_PAYLOAD_HANDLER':
            'app01.myjwt.jwt_response_payload_handler',
    
    }
    
    
  3. 使用postman测试,就看到我们自定义的返回了
    image-20230209192008291

jwt认证类

以后接口要登陆后才能访问使用

  1. 在视图类上加一个认证类,一个权限类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]
    
  2. postman测试

    请求头中key值叫Authorization
    请求头的value值是:jwt有效的token

image-20230209193026656

用户登录有权限,不登录没有权限

image-20230209193312978

image-20230209193500886

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",

image-20230209211125159

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)

image-20230209220555404

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
posted @ 2023-02-09 22:13  clever-cat  阅读(74)  评论(0编辑  收藏  举报