博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
rest_framework框架之认证功能的使用和源码实现流程分析
阅读量:4095 次
发布时间:2019-05-25

本文共 13926 字,大约阅读时间需要 46 分钟。

rest_framework框架之认证的使用和源码实现流程分析

红尘小说网 https://wap.zuxs.net/

一、认证功能的源码流程

  • 创建视图函数

    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('删除一条订单')
  • 运行dispatch方法

    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
  • 查看initialize_request()函数

    Note

    在initialize_request()函数中返回了authenticators, 通过观察可以看出,authenticators的值来自于另外一个函数get_authenticators()。

    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  )
  • 找到函数self.get_authenticators()

    Note

    这个函数中实质上是把一个认证类列表实例化为对象列表进行返回,这里就可以得出在上一个函数中的authenticators是一个实例化对象列表。需要继续往源头找,查找authentication_classes

    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()是把获取到的类对象进行实例化操作
  • 查找authentication_classes类

    Note

    在自己编写的代码中如果定义了认证类,则执行自定义认证类,如果没有定义authentication_classes类,程序会从继承的类中去查找,视图类继承自APIView,所以在APIView中找到类authentication_classes。

    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

    从上述的逻辑可以看出最终要执行的是AUTHENTICATION_CLASSES,所有的程序中都是如果有自定义程序会覆盖掉框架封装好的,没有自定义,程序才会执行封装好的代码。AUTHENTICATION_CLASSES类是这个逻辑中最重要的一环。

  • 上边的代码查找到了最基本的Authentication_classes,并且得到加工后的request包含两部分内容:原生的request、Authentication_classes实例化后得到的对象列表,此时需要继续执行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)方法

    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生成的实例化列表类对象,循环的对象具有authenticate()属性/方法,可以直接调用,并通过条件语句判断,如果登陆返回元组,如果没有登陆返回错误提示。此时基本的逻辑已经梳理完成。

    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()方法,方法中直接调用框架自定义的api_settings.UNAUTHENTICATED_USER()类,如果存在user为AnonymousUser(匿名用户), auth为None,如果不存在,user和auth都直接赋值为None。

    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

二、自定义认证类

通过上述逻辑的整体分析,我们可以编写一个自定义的认证类供视图函数来调用,自定义的认证类必须具有两个方法:authenticate()和authenticate_header()方法,authenticate()必须返回一个元组,元组第一个元素为user,第二个元素为token对象

# 为测试程序临时创建的数据    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

    • 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)

转载地址:http://jlxii.baihongyu.com/

你可能感兴趣的文章
Java并发编程1-线程池
查看>>
CentOS7,玩转samba服务,基于身份验证的共享
查看>>
计算机网络-网络协议模型
查看>>
计算机网络-OSI各层概述
查看>>
Java--String/StringBuffer/StringBuilder区别
查看>>
mySQL--深入理解事务隔离级别
查看>>
分布式之redis复习精讲
查看>>
数据结构与算法7-栈
查看>>
线性数据结构学习笔记
查看>>
数据结构与算法14-跳表
查看>>
Java并发编程 | 一不小心就死锁了,怎么办?
查看>>
(python版)《剑指Offer》JZ01:二维数组中的查找
查看>>
(python版)《剑指Offer》JZ06:旋转数组的最小数字
查看>>
(python版)《剑指Offer》JZ13:调整数组顺序使奇数位于偶数前面
查看>>
(python版)《剑指Offer》JZ28:数组中出现次数超过一半的数字
查看>>
(python版)《剑指Offer》JZ30:连续子数组的最大和
查看>>
(python版)《剑指Offer》JZ32:把数组排成最小的数
查看>>
(python版)《剑指Offer》JZ02:替换空格
查看>>
JSP/Servlet——MVC设计模式
查看>>
使用JSTL
查看>>