• DRF之文档生成和JWT


    1 自定制频率

    # 自定制频率类,需要写两个方法
    	-# 判断是否限次:没有限次可以请求True,限次了不可以请求False
        	def allow_request(self, request, view):
        -# 限次后调用,显示还需等待多长时间才能再访问,返回等待的时间seconds
        	def wait(self):
                
    # 代码
    import time
    class IPThrottle():
        #定义成类属性,所有对象用的都是这个
        VISIT_DIC = {}
        def __init__(self):
            self.history_list=[]
        def allow_request(self, request, view):
            '''
            #(1)取出访问者ip
            #(2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走
            #(3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
            #(4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
            #(5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
            '''
    
            ip=request.META.get('REMOTE_ADDR')
            ctime=time.time()
            if ip not in self.VISIT_DIC:
                self.VISIT_DIC[ip]=[ctime,]
                return True
            self.history_list=self.VISIT_DIC[ip]   #当前访问者时间列表拿出来
            while True:
                if ctime-self.history_list[-1]>60:
                    self.history_list.pop() # 把最后一个移除
                else:
                    break
            if len(self.history_list)<3:
                self.history_list.insert(0,ctime)
                return True
            else:
                return False
    
        def wait(self):
            # 当前时间,减去列表中最后一个时间
            ctime=time.time()
    
            return 60-(ctime-self.history_list[-1])
    
    #全局使用,局部使用
    
    
    # SimpleRateThrottle源码分析
        def get_rate(self):
            """
            Determine the string representation of the allowed request rate.
            """
            if not getattr(self, 'scope', None):
                msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
                       self.__class__.__name__)
                raise ImproperlyConfigured(msg)
    
            try:
                return self.THROTTLE_RATES[self.scope]  # scope:'user' => '3/min'
            except KeyError:
                msg = "No default throttle rate set for '%s' scope" % self.scope
                raise ImproperlyConfigured(msg)
        def parse_rate(self, rate):
            """
            Given the request rate string, return a two tuple of:
            <allowed number of requests>, <period of time in seconds>
            """
            if rate is None:
                return (None, None)
            #3  mmmmm
            num, period = rate.split('/')  # rate:'3/min'
            num_requests = int(num)
            duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
            return (num_requests, duration)
        def allow_request(self, request, view):
            if self.rate is None:
                return True
            #当前登录用户的ip地址
            self.key = self.get_cache_key(request, view)  # key:'throttle_user_1'
            if self.key is None:
                return True
    
            # 初次访问缓存为空,self.history为[],是存放时间的列表
            self.history = self.cache.get(self.key, [])
            # 获取一下当前时间,存放到 self.now
            self.now = self.timer()
    
            # Drop any requests from the history which have now passed the
            # throttle duration
    
            # 当前访问与第一次访问时间间隔如果大于60s,第一次记录清除,不再算作一次计数
            # 10 20 30 40
            # self.history:[10:23,10:55]
            # now:10:56
            while self.history and  self.now - self.history[-1] >= self.duration:
                self.history.pop()
    
            # history的长度与限制次数3进行比较
            # history 长度第一次访问0,第二次访问1,第三次访问2,第四次访问3失败
            if len(self.history) >= self.num_requests:
                # 直接返回False,代表频率限制了
                return self.throttle_failure()
    
            # history的长度未达到限制次数3,代表可以访问
            # 将当前时间插入到history列表的开头,将history列表作为数据存到缓存中,key是throttle_user_1,过期时间60s
            return self.throttle_success()
    
    

    2 自动生成接口文档

    # 1 安装:pip install coreapi
    
    # 2 在路由中配置
    	from rest_framework.documentation import include_docs_urls
        urlpatterns = [
            ...
            path('docs/', include_docs_urls(title='站点页面标题'))
        ]
    #3 视图类:自动接口文档能生成的是继承自APIView及其子类的视图。
    	-1 ) 单一方法的视图,可直接使用类视图的文档字符串,如
            class BookListView(generics.ListAPIView):
                """
                返回所有图书信息.
                """
        -2)包含多个方法的视图,在类视图的文档字符串中,分开方法定义,如
            class BookListCreateView(generics.ListCreateAPIView):
                """
                get:
                返回所有图书信息.
                post:
                新建图书.
                """
        -3)对于视图集ViewSet,仍在类视图的文档字符串中封开定义,但是应使用action名称区分,如
            class BookInfoViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
            """
            list:
            返回图书列表数据
            retrieve:
            返回图书详情数据
            latest:
            返回最新的图书数据
            read:
            修改图书的阅读量
            """
    

    3 JWT

    jwt=Json Web token
    #原理
    """
    1)jwt分三段式:头.体.签名 (head.payload.sgin)
    2)头和体是可逆加密,让服务器可以反解出user对象;签名是不可逆加密,保证整个token的安全性的
    3)头体签名三部分,都是采用json格式的字符串,进行加密,可逆加密一般采用base64算法,不可逆加密一般采用hash(md5)算法
    4)头中的内容是基本信息:公司信息、项目组信息、token采用的加密方式信息
    {
    	"company": "公司信息",
    	...
    }
    5)体中的内容是关键信息:用户主键、用户名、签发时客户端信息(设备号、地址)、过期时间
    {
    	"user_id": 1,
    	...
    }
    6)签名中的内容时安全信息:头的加密结果 + 体的加密结果 + 服务器不对外公开的安全码 进行md5加密
    {
    	"head": "头的加密字符串",
    	"payload": "体的加密字符串",
    	"secret_key": "安全码"
    }
    """
    
    校验
    """
    1)将token按 . 拆分为三段字符串,第一段 头加密字符串 一般不需要做任何处理
    2)第二段 体加密字符串,要反解出用户主键,通过主键从User表中就能得到登录用户,过期时间和设备信息都是安全信息,确保token没过期,且时同一设备来的
    3)再用 第一段 + 第二段 + 服务器安全码 不可逆md5加密,与第三段 签名字符串 进行碰撞校验,通过后才能代表第二段校验得到的user对象就是合法的登录用户
    """
    
    drf项目的jwt认证开发流程(重点)
    """
    1)用账号密码访问登录接口,登录接口逻辑中调用 签发token 算法,得到token,返回给客户端,客户端自己存到cookies中
    
    2)校验token的算法应该写在认证类中(在认证类中调用),全局配置给认证组件,所有视图类请求,都会进行认证校验,所以请求带了token,就会反解出user对象,在视图类中用request.user就能访问登录的用户
    
    注:登录接口需要做 认证 + 权限 两个局部禁用
    """
    
    # 第三方写好的  django-rest-framework-jwt
    # 安装pip install djangorestframework-jwt
    
    # 新建一个项目,继承AbstractUser表()
    
    # 创建超级用户
    
    # 简单使用
     #urls.py
        from rest_framework_jwt.views import ObtainJSONWebToken,VerifyJSONWebToken,RefreshJSONWebToken,obtain_jwt_token
        path('login/', obtain_jwt_token),
    
    

    3.1 自定制auth认证类

    from rest_framework_jwt.authentication import BaseAuthentication,BaseJSONWebTokenAuthentication
    from rest_framework.exceptions import AuthenticationFailed
    from rest_framework_jwt.authentication import jwt_decode_handler
    from rest_framework_jwt.authentication import get_authorization_header,jwt_get_username_from_payload
    from rest_framework import exceptions
    class MyToken(BaseJSONWebTokenAuthentication):
        def authenticate(self, request):
            jwt_value=str(request.META.get('HTTP_AUTHORIZATION'))
            # 认证
            try:
                payload = jwt_decode_handler(jwt_value)
    
            except Exception:
                raise exceptions.AuthenticationFailed("认证失败")
            user=self.authenticate_credentials(payload)
            return user,None
        
    

    3.2 控制用户登录后才能访问,和不登录就能访问

    # 1 控制用户登录后才能访问,和不登录就能访问
    from rest_framework.permissions import IsAuthenticated
    class OrderAPIView(APIView):# 登录才能
        authentication_classes = [JSONWebTokenAuthentication,]
        # 权限控制
        permission_classes = [IsAuthenticated,]
        def get(self,request,*args,**kwargs):
            return Response('这是订单信息')
    
    
    class UserInfoAPIView(APIView):# 不登录就可以
        authentication_classes = [JSONWebTokenAuthentication,]
        # 权限控制
        # permission_classes = [IsAuthenticated,]
        def get(self,request,*args,**kwargs):
            return Response('UserInfoAPIView')
    

    3.3 控制登录接口返回的数据格式

    # 2 控制登录接口返回的数据格式
    	-第一种方案,自己写登录接口
        -第二种写法,用内置,控制登录接口返回的数据格式
        	-jwt的配置信息中有这个属性
        	    'JWT_RESPONSE_PAYLOAD_HANDLER':
        'rest_framework_jwt.utils.jwt_response_payload_handler',
        	-重写jwt_response_payload_handler,配置成咱们自己的
    

    3.4 自定义基于jwt的权限类

    # 3 自定义基于jwt的权限类
    from rest_framework.authentication import BaseAuthentication  # 基于它
    from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication # 基于它
    from rest_framework.exceptions import AuthenticationFailed
    # from rest_framework_jwt.authentication import jwt_decode_handler
    from rest_framework_jwt.utils import jwt_decode_handler # 跟上面是一个
    import jwt
    
    from api import models
    # class MyJwtAuthentication(BaseAuthentication):
    #     def authenticate(self, request):
    #         jwt_value=request.META.get('HTTP_AUTHORIZATION')
    #         if jwt_value:
    #             try:
    #             #jwt提供了通过三段token,取出payload的方法,并且有校验功能
    #                 payload=jwt_decode_handler(jwt_value)
    #             except jwt.ExpiredSignature:
    #                 raise AuthenticationFailed('签名过期')
    #             except jwt.InvalidTokenError:
    #                 raise AuthenticationFailed('用户非法')
    #             except Exception as e:
    #                 # 所有异常都会走到这
    #                 raise AuthenticationFailed(str(e))
    #             # 因为payload就是用户信息的字典
    #             print(payload)
    #             # return payload, jwt_value
    #             # 需要得到user对象,
    #             # 第一种,去数据库查
    #             # user=models.User.objects.get(pk=payload.get('user_id'))
    #             # 第二种不查库
    #             user=models.User(id=payload.get('user_id'),username=payload.get('username'))
    #             return user,jwt_value
    #         # 没有值,直接抛异常
    #         raise AuthenticationFailed('您没有携带认证信息')
    
    
    class MyJwtAuthentication(BaseJSONWebTokenAuthentication):
        def authenticate(self, request):
            jwt_value=request.META.get('HTTP_AUTHORIZATION')
            if jwt_value:
                try:
                #jwt提供了通过三段token,取出payload的方法,并且有校验功能
                    payload=jwt_decode_handler(jwt_value)
                except jwt.ExpiredSignature:
                    raise AuthenticationFailed('签名过期')
                except jwt.InvalidTokenError:
                    raise AuthenticationFailed('用户非法')
                except Exception as e:
                    # 所有异常都会走到这
                    raise AuthenticationFailed(str(e))
                user=self.authenticate_credentials(payload)
                return user,jwt_value
            # 没有值,直接抛异常
            raise AuthenticationFailed('您没有携带认证信息')
    

    3.5 手动签发token(多方式登录)

    # 使用用户名,手机号,邮箱,都可以登录#
    # 前端需要传的数据格式
    {
    "username":"lqz/1332323223/33@qq.com",
    "password":"lqz12345"
    }
    # 视图
    from rest_framework.views import APIView
    from rest_framework.viewsets import ViewSetMixin, ViewSet
    
    from app02 import ser
    class Login2View(ViewSet):  # 跟上面完全一样
        def login(self, request, *args, **kwargs):
            # 1 需要 有个序列化的类
            login_ser = ser.LoginModelSerializer(data=request.data,context={'request':request})
            # 2 生成序列化类对象
            # 3 调用序列号对象的is_validad
            login_ser.is_valid(raise_exception=True)
            token=login_ser.context.get('token')
            # 4 return
            return Response({'status':100,'msg':'登录成功','token':token,'username':login_ser.context.get('username')})
        
    # 序列化类
    from rest_framework import serializers
    from api import models
    import re
    from rest_framework.exceptions import ValidationError
    
    from rest_framework_jwt.utils import jwt_encode_handler,jwt_payload_handler
    class LoginModelSerializer(serializers.ModelSerializer):
        username=serializers.CharField()  # 重新覆盖username字段,数据中它是unique,post,认为你保存数据,自己有校验没过
        class Meta:
            model=models.User
            fields=['username','password']
    
        def validate(self, attrs):
    
            print(self.context)
    
            # 在这写逻辑
            username=attrs.get('username') # 用户名有三种方式
            password=attrs.get('password')
            # 通过判断,username数据不同,查询字段不一样
            # 正则匹配,如果是手机号
            if re.match('^1[3-9][0-9]{9}$',username):
                user=models.User.objects.filter(mobile=username).first()
            elif re.match('^.+@.+$',username):# 邮箱
                user=models.User.objects.filter(email=username).first()
            else:
                user=models.User.objects.filter(username=username).first()
            if user: # 存在用户
                # 校验密码,因为是密文,要用check_password
                if user.check_password(password):
                    # 签发token
                    payload = jwt_payload_handler(user)  # 把user传入,得到payload
                    token = jwt_encode_handler(payload)  # 把payload传入,得到token
                    self.context['token']=token
                    self.context['username']=user.username
                    return attrs
                else:
                    raise ValidationError('密码错误')
            else:
                raise ValidationError('用户不存在')
    

    3.6 jwt的配置参数

    # jwt的配置
    import datetime
    JWT_AUTH={
        'JWT_RESPONSE_PAYLOAD_HANDLER':'app02.utils.my_jwt_response_payload_handler',
        'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), # 过期时间,手动配置
    }
    
  • 相关阅读:
    C# 两个窗体中相互切换的方法
    Linq对DataTable数据的分组统计
    如何让窗体大小随着控件的大小变化而变化
    两张表解决用户自定义数据库之思路
    picturebox显示用字符串代表图片名称的(已导入资源的)图片
    如何将access高版本数据库保存为低版本的?
    Microsoft.Jet.OLEDB.4.0和Microsoft.ACE.OLEDB.12.0的区别
    条件编译解决AutoCAD多版本问题
    一个爬取股票信息的爬虫程序
    Python爬虫抓取东方财富网股票数据并实现MySQL数据库存储
  • 原文地址:https://www.cnblogs.com/Henry121/p/13307726.html
Copyright © 2020-2023  润新知