在聊APIView以前, 咱们先重温一下django2.x的CBV流程python
a. 对于django而言, 当浏览器请求到达以后,按照规则首先会通过各大中间件(Middleware)(process_request, process_view, process_response), 而与CBV挂钩的即是各中间件的process_view方法,以CSRFViewMiddleware为例,django会默认开启全局CSRF验证,而在实际开发中,一部分视图处理类是不须要CSRF验证的,因此咱们须要在处理类方法上加上@method_decorator(csrf_exempt)装饰器来取消验证,下面是CSRFViewMiddleware类下的process_view的部分源码,django
def process_view(self, request, callback, callback_args, callback_kwargs): if getattr(request, 'csrf_processing_done', False): return None # Wait until request.META["CSRF_COOKIE"] has been manipulated before # bailing out, so that get_token still works if getattr(callback, 'csrf_exempt', False): return None
b.为何执行了视图类的as_view(), 便可以处理各类类型(GET, POST, DELETE, HEAD等等)请求呢? 咱们知道,视图类是利用dispatch(self, *args, **kwargs)的反射来实现请求(GET/POST......)与处理方法( get(self, request) / post(self, request) )之间的映射,因此实际上as_view()方法是调用了视图类中的dispatch来实现调用具体的请求处理方法的。下面是as_view()的部分源码:后端
1 def as_view(cls, **initkwargs): 2 """Main entry point for a request-response process.""" 3 for key in initkwargs: 4 if key in cls.http_method_names: 5 raise TypeError("You tried to pass in the %s method name as a " 6 "keyword argument to %s(). Don't do that." 7 % (key, cls.__name__)) 8 if not hasattr(cls, key): 9 raise TypeError("%s() received an invalid keyword %r. as_view " 10 "only accepts arguments that are already " 11 "attributes of the class." % (cls.__name__, key)) 12 13 def view(request, *args, **kwargs): 14 self = cls(**initkwargs) 15 if hasattr(self, 'get') and not hasattr(self, 'head'): 16 self.head = self.get 17 self.request = request 18 self.args = args 19 self.kwargs = kwargs 20 return self.dispatch(request, *args, **kwargs) 21 view.view_class = cls 22 view.view_initkwargs = initkwargs 23 24 # take name and docstring from class 25 update_wrapper(view, cls, updated=()) 26 27 # and possible attributes set by decorators 28 # like csrf_exempt from dispatch 29 update_wrapper(view, cls.dispatch, assigned=()) 30 return view
了解了CBV, 那APIView又是怎么一回事?api
a. APIView是View的子类,其处理请求流程与View一致,但APiView内部对原生request对象进行了封装,下面是其封装request的部分源码浏览器
能够看出原生request对象被赋到新建request对象的_request属性中,接下来即是基于APIView的认证了,它又是如何实现的呢?restful
在先后端分离的项目中, 经常须要判断用户是否登录, 其登陆状态的维持须要借助于令牌(Token),若是在不整合restframework框架的状况下,咱们要实现这一功能能够自定义一个校验装饰器来装饰每个须要登录以后才能执行的试图函数或者视图类方法,而在restframework中,它变得更加灵活与规范, 具体体如今APIView提供支持多重验证,验证与视图函数较低的耦合度,支持全局配置验证规则等等,下面咱们从APIView的源码中详细的了解这一过程:app
1. 从第一部分对apiview的分析中, 咱们能够知道, APIView新构建了一个request,该request对象不只封装了原生request对象的全部属性(_request),并且扩展了不少针对验证,访问频率限制,权限控制等等新的特性;框架
2. 就认证(authentication)而言,前后端分离
那么子类(基类为APIView)的authentication_classes属性为自定义的验证规则,那他又是如何执行这一规则的呢? 从第一部分的APIView建立新的request对象中咱们知道,其request对象的authenticators属性为get_authenticators()方法的返回值,那么进入get_authenticators():iview
怎么定义一个处理用户认证的类呢?答案位于rest_framework.authentication下的BaseAuthentication。 须要说明的是,继承该类的认证类必须拥有其authenticate(self, request, )以及authenticate_header(self, request)方法
欧克,到这里的话, 咱们来实战一下
咱们在名为testrestful中建立两张表,一张存用户基本信息,一张存用户token, 并在项目根目录下执行python manage.py makemigrations、python manage.py migrate命令,并手动添加一些用户数据到UserProfile
# models.py (testrestful)
from django.db import models from django.contrib.auth.models import User # Create your models here. class UserProfile(models.Model): # user = models.OneToOneField(to='User', on_delete=models.CASCADE) username = models.CharField(max_length=32, unique=True, null=False, blank=False) password = models.CharField(max_length=32, unique=False, null=False, blank=False) level_choice = ( (1, '普通用户'), (2, 'VIP'), (3, 'SVIP'), ) level = models.SmallIntegerField(choices=level_choice, null=False, blank=False) def __str__(self): return '[model: %s]' % self.username class UserToken(models.Model): user = models.OneToOneField(to='UserProfile',on_delete=models.CASCADE) token = models.CharField(max_length=64) expired = models.DateTimeField(null=True)
在testrestful目录下新建utils包,包下新建auth.py(也能够直接在views.py中建立, )
其流程是: 已注册的用户发起登录请求 ----> 认证处理 (用户名及密码正确则派发新的Token) ------> 用户保存token 后续请求附上token获得相应数据
from rest_framework import exceptions from rest_framework.authentication import BasicAuthentication, BaseAuthentication from testrestful import models import hashlib import uuid def md5Token(user: models.UserProfile)->str: """用于生成token""" # 用户名惟一, 并将其做为md5对象的salt hash = hashlib.md5(user.username.encode('utf-8')) cur_data = bytes(str(uuid.uuid1()), encoding='utf-8') hash.update(cur_data) return hash.hexdigest() class UserAuthentication(BaseAuthentication): """用于认证token""" def authenticate(self, request): token = request._request.GET.get('token', None) u_token_obj = models.UserToken.objects.get(token=token) if not u_token_obj: raise exceptions.AuthenticationFailed('用户身份验证失败!') else: # resframework 会将这两个元组返回给request, 以便后续操做 return (u_token_obj.user, u_token_obj) def authenticate_header(self, request): """当认证失败的时候, 返回给浏览器的响应头""" pass
若是用户经过认证, 则以(key1, key2) 做为执行返回值,并装载到request对象中,key1对应request.user,key2对应request.auth
那么对应的视图类就很好办了,只须要添加验证规则类就欧克
from django.http.response import JsonResponse from rest_framework.response import Response from rest_framework.views import APIView from testrestful import models from testrestful.utils.auth import UserAuthentication, md5Token class Login(APIView): """用于接收登陆请求,派发或更新token""" # 已经在配置中配置了全局认证类, 而匿名用户不须要认证 authentication_classes = [] msg = dict() def post(self, request): # 此时用户没有登陆,则返回默认匿名用户, 这一用户一样可以实现自定义 print('login_user:\t', request.user) username = request._request.GET.get('username', None) password = request._request.GET.get('password', None) try: user = models.UserProfile.objects.filter(username=username, password=password).first() # 派发token token = md5Token(user) # token存在则更新, 不存在则建立 models.UserToken.objects.update_or_create(user=user, defaults={'token': token}) self.msg['flag'] = 1 self.msg['content'] = token print(self.msg) except Exception as e: print(e) self.msg['flag'] = 0 self.msg['content'] = "用户名或密码错误" return JsonResponse(self.msg) # Create your views here. class UserInfo(APIView): """用于获取用户信息""" authentication_classes = [UserAuthentication,] def dispatch(self, request, *args, **kwargs): return super(UserInfo, self).dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): # 在UserAuthentication中的authenticate方法返回的元组被添加到了request对象中,元组第一个对象就是request.user, 第二个就是request.auth print(request.user) print(request.auth) return Response("ok") def post(self, request): return Response("POST Response") def delete(self, request): return Response("DELETE Response")
下面以postman作测试:
a. 登录
b. 获取用户信息(需登录)
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ['testrestful.utils.auth.UserAuthentication', ], 'UNAUTHENTICATED_USER': lambda : '匿名用户(request.user)', 'UNAUTHENTICATED_TOKEN': lambda : '匿名用户(request.auth)', }
配置完成以后, 与CSRF类似, 须要用户认证的试图类就不须要authentication_classes这一行了,不须要用户认证的匿名用户视图类覆盖authentication_class就欧克
拿本例举例: