## 内容回顾 ### 认证类的请求执行流程—》源码分析 ```python 请求进来》路由匹配》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:视图类中配置的一个个的认证类的列表,如果没配,配置文件中,内置配置文件中 ``` ### 权限类的执行流程 ```python 请求进来》路由匹配》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) ) ``` ### 频率类的执行流程 ```python 请求进来》路由匹配》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 ```python 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 ```python 继承它,写代码少 只需要重写get_cache_key和scope类属性,配置文件配置 源码:allow_request》就是上面写的,可扩展性高,好多东西从配置文件取的 ``` ### 全局异常处理 ```python 源码中,在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. ##### 配置路由 ```python from rest_framework.documentation import include_docs_urls path('docs/', include_docs_urls(title='xx项目接口文档')), ``` 3. ##### 视图类,方法上,写注释即可 ###### 在类的最顶上加上注释,类下的所有方法都有 ```python class BookViwe(GenericViewSet, ListAPIView, CreateAPIView): """ 查询图书类 """ ```  ###### 在类的方法上加注释,就只要某一个方法有,会把类的注释信息顶掉 ```python def list(self, request, *args, **kwargs): """ 查询所有图书方法 :param request: :param args: :param kwargs: :return: 返回查询所有图书信息 """ ```  ###### 在序列化类或表模型的字段上加help_text,required。。。优先显示序列化类中的help_text信息 ```python id= serializers.IntegerField(help_text='书籍id') class Book(models.Model): name = models.CharField(max_length=32) price = models.IntegerField(help_text='书籍价格') ```  4. ##### 配置文件配置 ```python 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的构成 三部分,每部分用`.`分隔 ```python eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ ``` 第一部分:header,头 声明类型,这里是JWT 声明加密的算法 通常直接使用HMAC SHA256 公司信息。。。 第二部分:payload,荷载 存放有效信息的地方 过期时间 签发时间 用户id 用户名字。。 第三部分:signature,签名 第一部分和第二部分通过密钥+加密方式(HMAC SHA256)得到 #### jwt开发重点 登录接口—》签发token 认证类–》jwt认证 ### base64编码和解码 ```python 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编码 ```python ''' 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签发和认证 #### 使用步骤 1. 安装 2. 快速签发token–>登录接口,路由配置 使用django,user表实现快速认证 ```python path('login/', obtain_jwt_token), ``` 3. postman,测试 ```python http://127.0.0.1:8000/login/发送post请求,携带username和password ```  认证成功返回token  ### 定制返回格式 以后如果是基于auth的User标签token,就可以不自己写了,但是登录接口返回格式,只有token,不符合公司规范 #### 使用步骤 1. 写个函数jwt_response_payload_handler ```python def jwt_response_payload_handler(token, user=None, request=None): return { 'code': 100, 'msg': '登录成功', 'username': user.username, 'token': token } ``` 2. 配置项目配置 ```python JWT_AUTH = { 'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.myjwt.jwt_response_payload_handler', } ``` 3. 使用postman测试,就看到我们自定义的返回了  ### jwt认证类 以后接口要登陆后才能访问使用 1. 在视图类上加一个认证类,一个权限类class ```python 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  用户登录有权限,不登录没有权限   ### simplejwt ```python from rest_framework_simplejwt.views import token_obtain_pair path('login/', token_obtain_sliding), ``` settings.py #### 认证生成token的属性不同,解析的时候也是不同的,所以需要配置相同的认证token属性的类,其实就是对象加密属性 ```python # simplejwt的加密属性 SIMPLE_JWT={ "AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.SlidingToken",), # 这个需要跟你路由中使用的生成类是一个 } # 这个是定制值的第一个字符串 "AUTH_HEADER_TYPES": ("Bearer",), # 这个是定制请求头字符串 "AUTH_HEADER_NAME": "HTTP_AUTHORIZATION", ```  view.py ```python 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 ```python header面key为AUTHORIZATION values值为Bearer token ``` #### 认证token类 ```python 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类,然后重写,然后再里面加就行了,因为目前暂时没发现可以再配置中替换返回数据的,所以暂时就用这个 ```python 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属性 ```python 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 ``` Last modification:February 9th, 2023 at 10:24 pm © 允许规范转载 Support 如果觉得我的文章对你有用,请随意赞赏 ×Close Appreciate the author Sweeping payments
Comment here is closed