主要内容:
- 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",