###rest_framework框架之认证的使用和源码实现流程分析 ####1、认证功能的源码流程html
Note 建立视图函数后,前端发起请求,url分配路由,执行视图类,视图类中执行对应方法必须通过dispatch()即调度方法前端
from rest_framework.views import APIView from django.shortcuts import HttpResponse import json class DogView(APIView): def get(self, request, *args, **kwargs): result = { 'code': '10000', 'msg': '数据建立成功' } return HttpResponse(json.dumps(result)) def post(self, request, *args, **kwargs): return HttpResponse('建立一条订单') def put(self, request, *args, **kwargs): return HttpResponse('更新一条订单') def delete(self, request, *args, **kwargs): return HttpResponse('删除一条订单')
Note 若是本身定义了dispatch方法,则程序运行自定义方法,若是没有,程序运行源码中的dispatch方法。从dispatch方法中能够找到原生request在做为参数传递后被initialize_request()函数进行了加工,经过加工的request得到的值包括原生的request和BaseAuthentication实例化对象,因此咱们须要找到initialize_request()。数据库
def dispatch(self, request, *args, **kwargs): """ `.dispatch()` is pretty much the same as Django's regular dispatch, but with extra hooks for startup, finalize, and exception handling. """ self.args = args self.kwargs = kwargs request = self.initialize_request(request, *args, **kwargs) ''' 对原生的request进行加工,得到到的request已经不是原来的request,还包括了其余的参数, 能够经过新的request获取到内部包含的参数 加工后的request : Restquest( request, parsers=self.get_parsers(), authenticators=self.get_authenticators(), negotiator=self.get_content_negotiator(), parser_context=parser_context )) ''' self.request = request self.headers = self.default_response_headers # deprecate? try: self.initial(request, *args, **kwargs) # 把加工后的request看成参数传递给了initial()函数 # 须要把在这里查找initial()函数 # Get the appropriate handler method if request.method.lower() in self.http_method_names: handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed response = handler(request, *args, **kwargs) except Exception as exc: response = self.handle_exception(exc) self.response = self.finalize_response(request, response, *args, **kwargs) return self.response
Note 在initialize_request()函数中返回了authenticators, 经过观察能够看出,authenticators的值来自于另一个函数get_authenticators()。django
def initialize_request(self, request, *args, **kwargs): """ Returns the initial request object. """ parser_context = self.get_parser_context(request) return Request( request, # 原生request parsers=self.get_parsers(), authenticators=self.get_authenticators(), # authenticators获取到的是实例化后的认证类对象列表,即[Foo(), Bar()] negotiator=self.get_content_negotiator(), parser_context=parser_context )
Note 这个函数中实质上是把一个认证类列表实例化为对象列表进行返回,这里就能够得出在上一个函数中的authenticators是一个实例化对象列表。须要继续往源头找,查找authentication_classesjson
def get_authenticators(self): """ Instantiates and returns the list of authenticators that this view can use. """ # 例如self.authentication_classes = [foo, bar] return [auth() for auth in self.authentication_classes] # 列表生成式,auth获取到的是列表中的类,auth()是把获取到的类对象进行实例化操做
Note 在本身编写的代码中若是定义了认证类,则执行自定义认证类,若是没有定义authentication_classes类,程序会从继承的类中去查找,视图类继承自APIView,因此在APIView中找到类authentication_classes。api
class APIView(View): # The following policies may be set at either globally, or per-view. renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES parser_classes = api_settings.DEFAULT_PARSER_CLASSES authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES # 继承自APIView中的api_settings.DEFAULT_AUTHENTICATION_CLASSES类 throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS metadata_class = api_settings.DEFAULT_METADATA_CLASS versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
Summary <font color='red'>从上述的逻辑能够看出最终要执行的是AUTHENTICATION_CLASSES,全部的程序中都是若是有自定义程序会覆盖掉框架封装好的,没有自定义,程序才会执行封装好的代码。AUTHENTICATION_CLASSES类是这个逻辑中最重要的一环。</font>app
上边的代码查找到了最基本的Authentication_classes,而且获得加工后的request包含两部份内容:<font color='red'>原生的request、Authentication_classes实例化后获得的对象列表</font>,此时须要继续执行dispatch(),执行到try语句时,加工后的request做为参数传递给initial()函数,并执行该函数,此时须要到request.py中查找initial()函数。框架
self.request = request self.headers = self.default_response_headers # deprecate? try: self.initial(request, *args, **kwargs) # Get the appropriate handler method if request.method.lower() in self.http_method_names: handler = getattr(self, request.method.lower(),self.http_method_not_allowed) else: handler = self.http_method_not_allowed response = handler(request, *args, **kwargs) except Exception as exc: response = self.handle_exception(exc) self.response = self.finalize_response(request, response, *args, **kwargs) return self.response
查找initial()方法,在该方法中找到perform_authentication(request)方法,继续查找perform_authentication(request)方法ide
def initial(self, request, *args, **kwargs): """ Runs anything that needs to occur prior to calling the method handler. """ self.format_kwarg = self.get_format_suffix(**kwargs) # Perform content negotiation and store the accepted info on the request neg = self.perform_content_negotiation(request) request.accepted_renderer, request.accepted_media_type = neg # Determine the API version, if versioning is in use. version, scheme = self.determine_version(request, *args, **kwargs) request.version, request.versioning_scheme = version, scheme # Ensure that the incoming request is permitted self.perform_authentication(request) self.check_permissions(request) self.check_throttles(request)
perform_authentication方法中调用了request.py中的Request类的user()方法函数
def perform_authentication(self, request): """ Perform authentication on the incoming request. Note that if you override this and simply 'pass', then authentication will instead be performed lazily, the first time either `request.user` or `request.auth` is accessed. """ request.user
在Request类中查找到request被传递进行,原生的参数在调用的时候格式为:request._request, 加工后的直接是request.属性
class Request: """ Wrapper allowing to enhance a standard `HttpRequest` instance. Kwargs: - request(HttpRequest). The original request instance. - parsers_classes(list/tuple). The parsers to use for parsing the request content. - authentication_classes(list/tuple). The authentications used to try authenticating the request's user. """ def __init__(self, request, parsers=None, authenticators=None, negotiator=None, parser_context=None): assert isinstance(request, HttpRequest), ( 'The `request` argument must be an instance of ' '`django.http.HttpRequest`, not `{}.{}`.' .format(request.__class__.__module__, request.__class__.__name__) ) self._request = request # 加工后的request被做为参数传递,那么传递后相对于本类即为原生的request。 self.parsers = parsers or () self.authenticators = authenticators or () self.negotiator = negotiator or self._default_negotiator() self.parser_context = parser_context self._data = Empty self._files = Empty self._full_data = Empty self._content_type = Empty self._stream = Empty
若是进行认证,必须经过user,此时须要查找user程序是否存在,在Request类中找到了user方法,user()方法执行了_authenticate(),查找_authenticate()
@property def user(self): """ Returns the user associated with the current request, as authenticated by the authentication classes provided to the request. """ if not hasattr(self, '_user'): with wrap_attributeerrors(): self._authenticate() # 执行_authenticate() return self._user
查找_authenticate(),在_authenticate()方法中查找到Authenticator_classes生成的实例化列表类对象,循环的对象具备<font color='red'>authenticate()属性/方法</font>,能够直接调用,并经过条件语句判断,若是登录返回元组,若是没有登录返回错误提示。此时基本的逻辑已经梳理完成。
def _authenticate(self): """ Attempt to authenticate the request using each authentication instance in turn. """ for authenticator in self.authenticators: try: user_auth_tuple = authenticator.authenticate(self) # 若是有返回值,继续执行 except exceptions.APIException: raise self._not_authenticated() # 没有返回值则抛出_not_authenticated()异常 if user_auth_tuple is not None: self._authenticator = authenticator self.user, self.auth = user_auth_tuple # authenticate()方法返回的元组存在,那么把元组的内容分别赋值给user, auth return self._not_authenticated()
查找异常处理方法_not_authenticated(),当前边的方法判断后没有收到元组数据,程序抛出了异常,这个异常执行_not_authenticated()方法,<font color='red'>方法中直接调用框架自定义的api_settings.UNAUTHENTICATED_USER()类,若是存在user为AnonymousUser(匿名用户), auth为None,若是不存在,user和auth都直接赋值为None。</font>
def _not_authenticated(self): """ Set authenticator, user & authtoken representing an unauthenticated request. Defaults are None, AnonymousUser & None. """ self._authenticator = None if api_settings.UNAUTHENTICATED_USER: self.user = api_settings.UNAUTHENTICATED_USER() else: self.user = None if api_settings.UNAUTHENTICATED_TOKEN: self.auth = api_settings.UNAUTHENTICATED_TOKEN() else: self.auth = None
####2、自定义认证类 经过上述逻辑的总体分析,咱们能够编写一个自定义的认证类供视图函数来调用,<font color='red'>自定义的认证类必须具备两个方法:authenticate()和authenticate_header()方法,authenticate()必须返回一个元组,元组第一个元素为user,第二个元素为token对象</font>
# 为测试程序临时建立的数据 ORDER_DICT = { 1: { 'name': 'dog', 'age': 2, 'gender': 'male' }, 2: { 'name': 'cat', 'age': 3, 'gender': 'female' } } # 自定义Authentication_classes from rest_framework import exceptions from api.models import UserToken class MyAuthentication(object): def authenticate(self, request, *args, **kwargs): token = request._request.GET.get('token') token_obj = UserToken.objects.filter(token=token).first() if not token_obj: raise exceptions.AuthenticationFailed("用户还没有登录") return (token_obj.user, token_obj) def authenticate_header(self, request): pass # 生成随机字符串token def md5(username): # 以用户post传过来的username和时间来做为参数,随机生成token, # 须要注意的是在建立models是username字段必须是惟一的。 import time import hashlib ctime = str(time.time) m = md5.hashlib(ytes(username, encodig='utf-8')) # 生成的随机字符串编码为utf-8 m.update(bytes(ctime, encoding='utf-8')) return m.hexdigest() # 建立认证视图 from rest_framework.views import APIView from api.models import UserInfo from django.http import JsonResponse class AuthView(APIView): def post(self, request, *args, **kwargs): # 虽然是验证信息,也是须要用户提交过来信息的,因此这里是post方法 result = { 'code': '1000', 'msg': None } try: username = request._request.GET.get('username') password = request._request.GET.get('password') user_obj = UserInfo.objects.filter(username=username, password=password).first() if not user_obj: result['code'] = '1001' result['msg'] = "用户不存在" # 若是不存在返回不存在异常 token = md5(username) # 建立函数生成token(随机字符串) result['token'] = token UserToken.objects.update_or_create(user=user_obj, defaults={'token': token}) # 如何实例化对象存在,则建立或者更新token except Exception as e: result['code'] = '1002' result['msg'] = '请求异常' return JsonResponse(result) # 建立处理request的视图函数 class OrderView(APIView): authentication_classes = [MyAuthentication,] def get(self, request, *args, **kwargs): result = { 'code': '1003', 'msg': None, 'data': None } try: result['data'] = ORDER_DICT except Exception as e: result['code'] = '1004', result['msg'] = '请求错误' return result
Note 在上边自定义的程序中,基本逻辑是:
- 首先是建立认证视图类,这个类解决的是哪些用户能够访问和获取到数据,认证视图中的思路是: dispatch调度方法获取到request后,进行加工,从加工的request中能够的到原生request经过post方法传过来的username和password信息,经过这些信息调用数据库查找匹配对象,若是没有抛出异常,若是存在,须要设置一个函数生成一个专属token
- 建立生成token函数,该函数须要用到time和hashlib两个第三方库,以request传过来的username和传入时间为参数进行设置生成
- 收到生成的token后认证视图将token做为参数返回,同时建立或者更新实例化对象的token字段信息,在用户再次登录后传过来的信息中就自动包含token
- 建立处理request的视图类,视图类中调用已经自定义好的authentication_classes,这个类专门用于认证信息,在该类中接收到token信息,并与数据库中的验证,若是验证不一致,抛出异常,反之,则返回一个元组信息,并继续执行视图类。须要注意的是,authentication_classes中能够存在多个自定义的认证类,但通常用使用的都是一个。
- 验证成功后dispatch调度方法执行对应的方法,并返回值给前端页面。
- BaseAuthentication类中是两个方法authenticate()和authenticate_header(), 咱们在自定义认证类的时候须要继承自基类,而且对这两个进行重写,若是不重写,系统自动抛出异常。
- 其余认证类:BasicAuthentication认证 通常程序中用到的是咱们自定义的认证类来进行开发
方式一:全局使用,须要在settings.py文件中设置
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ # 'rest_framework.authentication.BasicAuthentication', # 'rest_framework.authentication.SessionAuthentication', 'api.views.Authentication' # 这里是经过路径的方式把自定义的认证类加载到全局文件中 ] }
方式二:局部使用,须要在视图类中调用具体的自定义认证类
class OrderView(APIView): ''' 用于订单相关业务 ''' authentication_classes = [Authentication,] def get(self, request, *args, **kwargs): result = { 'code': '1000', 'msg': None, 'data': None } try: result['data'] = ORDER_DICT except Exception as e: result['code': '1001'] result['msg': '访问出错'] return JsonResponse(result)
原文出处:https://www.cnblogs.com/ddzc/p/12125070.html