rest_framework框架之認證功能的使用和源碼實現流程分析

###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('刪除一條訂單')
  • 運行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()。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
    )
  • 找到函數self.get_authenticators()

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()是把獲取到的類對象進行實例化操做
  • 查找authentication_classes類

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

相關文章
相關標籤/搜索