day 091 DRF -- 版本 | 认证 | 权限 | 限制

主要内容:

  • 1.版本控制
  • 2.认证
  • 3.权限
  • 4.限制

1.版本控制

1.1 版本控制的缘由

  • API 版本控制允许我们在不同的客户端之间更改行为(同一个接口的不同版本会返回不同的数据)。 DRF提供了许多不同的版本控制方案。
  • 可能会有一些客户端因为某些原因不再维护了,但是我们后端的接口还要不断的更新迭代,这个时候通过版本控制返回不同的内容就是一种不错的解决方案

1.2 DRF - 版本控制方案

       

1.3 版本控制系统的使用

  • (1)在settings文件中的配置(全局配置)
    #setting是文件中DRF的配置
    REST_FRAMEWORK = {
        ...
        'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
        'DEFAULT_VERSION': 'v1',  # 默认的版本
        'ALLOWED_VERSIONS': ['v1', 'v2'],  # 有效的版本
        'VERSION_PARAM': 'version',  # 版本的参数名与URL conf中一致
    }
  • (2) 路由
    urlpatterns = [
        ...
        url(r'^(?P<version>[v1|v2]+)/publishers/$', views.PublisherViewSet.as_view({'get': 'list', 'post': 'create'})),
        url(r'^(?P<version>[v1|v2]+)/publishers/(?P<pk>d+)/$', views.PublisherViewSet.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
    
    ]
  • (3)视图
    #不同版本使用不同的序列化类
    class PublisherViewSet(ModelViewSet):
    
        def get_serializer_class(self):
            """不同的版本使用不同的序列化类"""
            if self.request.version == 'v1':
                return PublisherModelSerializerVersion1
            else:
                return PublisherModelSerializer
        queryset = models.Publisher.objects.all()

注:局部配置:

# 可以在视图中设置versioning_class属性,如下:
class
PublisherListView(ListCreateAPIView): queryset = models.Publisher.objects.all() serializer_class = PublisherModelSerializer def get_queryset(self): if self.request.version == 'v1': return models.Publisher.objects.all()[:2] else: return self.queryset.all()

2. 认证  

2.1 概述

身份验证是将传入请求与一组标识凭据(例如请求来自的用户或其签名的令牌)相关联的机制。然后 权限 和 限制 组件决定是否拒绝这个请求。

简单来说就是:

  • 认证确定了你是谁
  • 权限确定你能不能访问某个接口
  • 限制确定你访问某个接口的频率

REST framework 提供了一些开箱即用的身份验证方案,并且还允许你实现自定义方案。

                        

2.2基于Token的认证方案

  • (1) model设计
    class UserInfo(models.Model):
        name = models.CharField(max_length=32)
        password = models.CharField(max_length=32)
        vip =models.BooleanField(default=False)
        token = models.CharField(max_length=128,blank=True,null=True)
  • (2) url
    #此处是又创建了APP在根路由通过include实现路由的分发
    urlpatterns = [
        url(r'^reg/$', views.RegView.as_view()),
        url(r'^login/$', views.LoginView.as_view()),
        url(r'^test_auth/$', views.TestAuthView.as_view()),  #测试登录认证
    ]
  • (3)认证类
    from rest_framework.authentication import BaseAuthentication
    from rest_framework.exceptions import AuthenticationFailed
    from BAR import models
    
    class MyAuth(BaseAuthentication):
        #重写authenticate方法
        def authenticate(self, request):
            # 必须返回元组,或者抛出 AuthenticationFailed 异常
            token = request.query_params.get('token')
            if token:
                #如果请求的URL中携带了token参数
                user_obj = models.UserInfo.objects.filter(token=token).first()
                if user_obj:
                    #如果token是有效的
                    return user_obj,token
                else:
                    raise  AuthenticationFailed('无效的token')
            else:
                raise AuthenticationFailed('请求的URL必须携带token参数')
  • (4)视图
    from rest_framework.views import APIView
    from BAR import models
    from rest_framework.response import Response
    import uuid
    
    class RegView(APIView):
        '''
        注册用户类
        '''
        def post(self,request):
            name = request.data.get('name')
            pwd = request.data.get('password')
            re_pwd = request.data.get('password')
            if name and pwd:
                if re_pwd == pwd:
                    models.UserInfo.objects.create(name=name,password=pwd)
                    return Response('账号注册成功')
                else:
                    raise Response('两次输入的密码不一致')
            else:
                return Response('无效的参数')
    
    class LoginView(APIView):
        def post(self,request):
            name = request.data.get('name')
            pwd = request.data.get('password')
            if name and pwd:
                user_obj = models.UserInfo.objects.filter(name=name,password=pwd).first()
                if user_obj:
                    #登录成功
                    #生成token(事件戳+mac地址)
                    token = uuid.uuid1().hex
                    #保存在用户表中
                    user_obj.token = token
                    user_obj.save()
                    #给用户返回
                    return Response ({'error_no':0,'token':token})
                else:
                    #用户名或者密码错误
                    return Response ({'error_no':1,'error':'用户名或密码错误'})
            else:
                return Response('无效的参数')
    
  • 局部配置
    from BAR.auth import  MyAuth
    
    class TestAuthView(APIView):
        # 视图级别的认证
        authentication_classes = [MyAuth, ]
    
        def get(self,request):
            return Response('这个视图里面的数据只有登录后才能看到')
  • 也可以在全局配置
    #在settings文件中
    REST_FRAMEWORK = {
        ...
        'DEFAULT_AUTHENTICATION_CLASSES': ['auth_demo.auth.MyAuth', ]
        #列表 -- 可以是多个
    }

: 一般都是全局配置,极个别情况下会给某个视图配置   局部的配置优先级高于全局配置

另外:authenticate方法返回值: 返回元组,元组的第一个元素赋值给 request.user 第二个元素复制给了request.auth

2.3 authenticate方法 抛错后:

 

注;从上述可以得出当 捕获到报错(raise),此时执行的  _not_authenticated  方法的return 结果 : user &auth 都赋值为None 

3.权限

自定义一个权限类 (只有VIP用户才能看的内容)

  • 3.1自定义权限类
    from rest_framework.permissions import BasePermission
    
    class MyPermission(BasePermission):
        message = '只有VIP才能访问'
    
        def has_permission(self, request, view):
    #通过上面的认证源码得知:当不输入token参数或者未登录,则 user ,auth 均为None,当auth存在则此时的user不为None
    if not request.auth: return False #当有Vip才有权限访问 #if request.user 当前经过认证的用户对象 if request.user.vip: return True else: #如果不是Vip就拒绝的范围 return False
  • 3.2 视图
    from BAR.auth import  MyAuth
    from BAR.permissions import MyPermission
    
    class TestAuthView(APIView):
        authentication_classes = [MyAuth, ]
        permission_classes = [MyPermission, ]
    
        def get(self,request):
            return Response('这个视图里面的数据只有登录后才能看到')

注:可以全局范围配置

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": ["BAR.auth.MyAuth", ],
    "DEFAULT_PERMISSION_CLASSES": ["BAR.permissions.MyPermission", ]
}

关于报错; 

 'AnonymousUser' object has no attribute 'vip'   (匿名用户没有VIP权限,)

 原因:由于permissions.py中没有判断是否通过认证(即request.auth是否存在)

4.限制

4.1 自定义限制类

  • (1) throttle.py
    import time
    visit_record ={}
    class MyThrottle(object):
        def __init__(self):
            self.history = None
    
        def allow_request(self,request,view):
            print(request.META)
            #拿到当前的请求的ip作为访问记录的key
            ip = request.META.get('REMOTE_ADDR')
            now = time.time()
            if ip not in visit_record:
                visit_record[ip] = []
            #把当前的请求的访问记录拿出来保存到一个变量中
            history = visit_record[ip]
            self.history = history
            #循环访问历史,把超过10 秒钟的请求事件去掉
            while history and now - history[-1] >10:
                history.pop()
            if len(history) >=3:
                return False
            else:
                self.history.insert(0,now)
                return True
    
        def wait(self):
            now = time.time()
            return self.history[-1] +10 -now
  • (2) 视图
    from BAR.XXX import MyThrottle
    
    class TestAuthView(APIView):
        # authentication_classes = [MyAuth, ]
        # permission_classes = [MyPermission, ]
        throttle_classes = [MyThrottle, ]
    
        def get(self,request):
            return Response('这个视图里面的数据只有登录后才能看到')
  • 注, 全局使用
    #在settings文件中进行配置
    "DEFAULT_THROTTLE_CLASSES": ["BAR.throttle.MyThrottle", ],

4.2 使用内置限制类

  • (1) throttle.py
    #使用内置限制类
    from rest_framework.throttling import SimpleRateThrottle
    
    class VisitThrottle(SimpleRateThrottle):
    
        scope = "xxx"
    
        def get_cache_key(self, request, view):
            return self.get_ident(request)
  • (2) 全局配置
       #在settings文件中进行配置
       "DEFAULT_THROTTLE_CLASSES": ["BAR.XXX.VisitThrottle", ],
        "DEFAULT_THROTTLE_RATES": {
            "xxx": "1/s",
原文地址:https://www.cnblogs.com/wcx666/p/10274280.html