1. 程式人生 > >Rest_Framework之認證、許可權、頻率元件原始碼剖析

Rest_Framework之認證、許可權、頻率元件原始碼剖析

一:使用RestFramwork,定義一個檢視

from rest_framework.viewsets import ModelViewSet


class BookView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

認證、頻率和許可權元件都由繼承的ModelViewSet支援,所以要了解這三個元件的具體如何實現

對認證、頻率、許可權的管理就需要進入到其中檢視

二:首先來了解元件認證

由上圖可以看到ModelViewSet繼承了六個類,前面的五個並沒有元件的內容,不去管,接下來進入GenericViewSet類中看看

GenericViewSet這個類沒有具體的程式碼,但是可以看到它繼承了兩個類ViewSetMixin,和generics.GenericAPIView

ViewSetMixin

這個類中主要需要了解的是as_view這個在url中使用的方法,這個類只是重寫了as_view中的view方法,具體的核心程式碼如下

for method, action in actions.items():
    handler = getattr(self, action)
    setattr(self, method, handler)

簡單來說,就是把url中傳入的字典for迴圈,利用反射找到對應的方法重新設定get請求對應的函式

url(r'^authors/$', views.AuthorModelView.as_view({"get":"list","post":"create"})),

如上:在Django啟動後,views.AuthorModelView.as_view({"get":"list","post":"create"})的執行結果是一個閉包函式view

請求傳送進來,根據閉包函式外的actions:{"get":"list","post":"create"}設定self.get = list或者設定 self.post= create等等

由上可知,這個函式也與要找的元件關係不大。

generics.GenericAPIView

def get_queryset(self):
def get_object(self):
def get_serializer(self, *args, **kwargs):
def get_serializer_class(self):
def get_serializer_context(self):
def filter_queryset(self, queryset):
@property
def paginator(self):
def paginate_queryset(self, queryset):
def get_paginated_response(self, data):

類中方法與要找元件無關,繼續進入其父類中找

在父類APIView中的dispach方法中

self.initial(request, *args, **kwargs)這一段程式碼負責所有的認證、許可權和頻率管理

因為檢視的繼承複雜,現在需要搞清楚類的繼承關係和程式碼具體執行步驟,才好往下走

繼承關係圖

請求執行流程

Django啟動

url(r'^authors/$', views.AuthorModelView.as_view({"get":"list","post":"create"})) 在Django啟動時就執行,as_view的執行結果是一個閉包函式

view,由actions = {"get":"list","post":"create"}等引數包裹:

實際路由為:url(r'^authors/$', view)

請求到來:

根據繼承關係:請求到來執行的view函式是類ViewSetMixin中的閉包函式view

view原始碼

def view(request, *args, **kwargs):
    self = cls(**initkwargs)
    # We also store the mapping of request methods to actions,
    # so that we can later set the action attribute.
    # eg. `self.action = 'list'` on an incoming GET request.
    self.action_map = actions

    # Bind methods to actions
    # This is the bit that's different to a standard view
    for method, action in actions.items():
        handler = getattr(self, action)
        setattr(self, method, handler)

    if hasattr(self, 'get') and not hasattr(self, 'head'):
        self.head = self.get

    self.request = request
    self.args = args
    self.kwargs = kwargs

    # And continue as usual
    return self.dispatch(request, *args, **kwargs)

可以看到,在將self.get,self.post等方法對映之後,view方法最終返回了self.dispatch(request, *args, **kwargs)的執行結果

根據物件的屬性和方法查詢原則,self.dispatchfan方法呼叫的是類APIView中的dispatch方法

dispatch原始碼

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

dispatch的核心功能就是根據請求的方法不同,分發執行不同的程式碼,並最終返回結果。

在這裡我注意到,每次請求分發之前都會執行self.initial(request, *args, **kwargs) 這段程式碼,也就是說每次請求都會進入這裡執行。

initial原始碼

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)  # 頻率

initial中的核心程式碼是依次執行:

self.perform_authentication(request) # 認證

self.check_permissions(request) # 許可權

self.check_throttles(request) # 頻率

也就是:認證通過才會驗證許可權,許可權驗證通過才會驗證頻率

perform_authentication原始碼

def perform_authentication(self, request):
    request.user

perform_authentication中返回了request.user,首先要明白這個request來自於哪裡?

從dispatch中一路過來,request一直沒做處理,說明request至少來自於dispatch,APIView中dispatch的原始碼中有一行程式碼可以解釋request的來源

request = self.initialize_request(request, *args, **kwargs)
self.request = request

initialize_request原始碼

def initialize_request(self, request, *args, **kwargs):
    """
    Returns the initial request object.
    """
    parser_context = self.get_parser_context(request)

    return Request(
        request,
        parsers=self.get_parsers(),
        authenticators=self.get_authenticators(),
        negotiator=self.get_content_negotiator(),
        parser_context=parser_context
    )

 initialize_request程式碼中返回了一個Request類的物件,傳入了

request
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context

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

@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()
    return self._user

 

perform_authentication程式碼中執行的request.user就是執行的Request類的user方法

user方法中的程式碼程式碼表示如果沒有_user屬性就執行self._authenticate()

 _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:
                self._not_authenticated()
                raise

            if user_auth_tuple is not None:
                self._authenticator = authenticator
                self.user, self.auth = user_auth_tuple
                return

        self._not_authenticated()

 _authenticate:for迴圈self.authenticators並賦值給authenticator,然後執行authenticate方法

 首先要知道self.authenticators來自於哪裡?

回溯程式碼:

_authenticate中呼叫了self.authenticators。

self物件來自於user方法

user方法中的self物件Request的例項化物件

Request的例項化物件的例項化物件有一個屬性:

self.authenticators= authenticators or ()

authenticators 是一個Request類的例項化引數,預設為None,如果有傳入引數則為傳入的值

 在initialize_request原始碼中例項化時:authenticators=self.get_authenticators(),

return Request(
    request,
    parsers=self.get_parsers(),
    authenticators=self.get_authenticators(),
    negotiator=self.get_content_negotiator(),
    parser_context=parser_context
)

這時的self來自於呼叫initialize_request的物件

initialize_request在dispatch中被呼叫,dispatch的呼叫物件即是自定義的檢視類的例項化物件

也即使說self.get_authenticators()是檢視類呼叫的get_authenticators方法

 get_authenticators原始碼

def get_authenticators(self):

    return [auth() for auth in self.authentication_classes]

 get_authenticators中for迴圈檢視類的authentication_classes的屬性,加括號例項化組成一個列表返回

於是查詢物件的屬性,首先從物件自己找,然後從檢視類中找,如果找不到,在依照繼承關係從被繼承的類中找

在被檢視類所繼承的類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
    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

    # Allow dependency injection of other settings to make testing easier.
    settings = api_settings
api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)
APISettings類中並沒有DEFAULT_AUTHENTICATION_CLASSES屬性,自動觸發__getattr__方法
APISettings原始碼
class APISettings(object):

    def __init__(self, user_settings=None, defaults=None, import_strings=None):
        if user_settings:  # 如果user_settings有值執行下列程式碼
            self._user_settings = self.__check_user_settings(user_settings)
        self.defaults = defaults or DEFAULTS
        # defaults有值則賦給self.defaults,沒有則把DEFAULTS賦值給self.defaults
        self.import_strings = import_strings or IMPORT_STRINGS
        self._cached_attrs = set()

    @property
    def user_settings(self):
        if not hasattr(self, '_user_settings'): # 如果_user_settings沒有定義
            self._user_settings = getattr(settings, 'REST_FRAMEWORK', {})
            # 從Django專案的settings檔案中利用反射取出'REST_FRAMEWORK'的值賦給self._user_settings
        return self._user_settings

    def __getattr__(self, attr):  # 物件用.attr的方法查詢不到屬性時自動觸發
        if attr not in self.defaults: # 如果self.defaults中沒有查詢的屬性則報錯
            raise AttributeError("Invalid API setting: '%s'" % attr)

        try:
            # Check if present in user settings
            val = self.user_settings[attr]
            # 從self.user_settings執行返回的值中取出屬性attr的的值賦給val
        except KeyError:
            # Fall back to defaults
            val = self.defaults[attr]

        # Coerce import strings into classes
        if attr in self.import_strings:
            # 如果屬性attr在self.import_strings中通過反射取出對應的相應的方法或屬性做進一步處理
            val = perform_import(val, attr)

        # Cache the result
        self._cached_attrs.add(attr)
        setattr(self, attr, val) # 利用反射給檢視類物件設定一個屬性attr值為val
        return val
DEFAULTS = {
    # Base API policies
    'DEFAULT_RENDERER_CLASSES': (
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ),
    'DEFAULT_PARSER_CLASSES': (
        'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser'
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication'
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.AllowAny',
    ),
    'DEFAULT_THROTTLE_CLASSES': (),
    'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'rest_framework.negotiation.DefaultContentNegotiation',
    'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata',
    'DEFAULT_VERSIONING_CLASS': None,

    # Generic view behavior
    'DEFAULT_PAGINATION_CLASS': None,
    'DEFAULT_FILTER_BACKENDS': (),

    # Schema
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema',

    # Throttling
    'DEFAULT_THROTTLE_RATES': {
        'user': None,
        'anon': None,
    },
    'NUM_PROXIES': None,

    # Pagination
    'PAGE_SIZE': None,

    # Filtering
    'SEARCH_PARAM': 'search',
    'ORDERING_PARAM': 'ordering',

    # Versioning
    'DEFAULT_VERSION': None,
    'ALLOWED_VERSIONS': None,
    'VERSION_PARAM': 'version',

    # Authentication
    'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser',
    'UNAUTHENTICATED_TOKEN': None,

    # View configuration
    'VIEW_NAME_FUNCTION': 'rest_framework.views.get_view_name',
    'VIEW_DESCRIPTION_FUNCTION': 'rest_framework.views.get_view_description',

    # Exception handling
    'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
    'NON_FIELD_ERRORS_KEY': 'non_field_errors',

    # Testing
    'TEST_REQUEST_RENDERER_CLASSES': (
        'rest_framework.renderers.MultiPartRenderer',
        'rest_framework.renderers.JSONRenderer'
    ),
    'TEST_REQUEST_DEFAULT_FORMAT': 'multipart',

    # Hyperlink settings
    'URL_FORMAT_OVERRIDE': 'format',
    'FORMAT_SUFFIX_KWARG': 'format',
    'URL_FIELD_NAME': 'url',

    # Input and output formats
    'DATE_FORMAT': ISO_8601,
    'DATE_INPUT_FORMATS': (ISO_8601,),

    'DATETIME_FORMAT': ISO_8601,
    'DATETIME_INPUT_FORMATS': (ISO_8601,),

    'TIME_FORMAT': ISO_8601,
    'TIME_INPUT_FORMATS': (ISO_8601,),

    # Encoding
    'UNICODE_JSON': True,
    'COMPACT_JSON': True,
    'STRICT_JSON': True,
    'COERCE_DECIMAL_TO_STRING': True,
    'UPLOADED_FILES_USE_URL': True,

    # Browseable API
    'HTML_SELECT_CUTOFF': 1000,
    'HTML_SELECT_CUTOFF_TEXT': "More than {count} items...",

    # Schemas
    'SCHEMA_COERCE_PATH_PK': True,
    'SCHEMA_COERCE_METHOD_NAMES': {
        'retrieve': 'read',
        'destroy': 'delete'
    },
}

DEFAULTS
DEFAULT

在本例中檢視類中並沒有重寫authentication_classes,因此根據APISettings中的程式碼可知,程式首先在Django的settings檔案中查詢,由於settins檔案中沒有定義,因此丟擲異常,最終從DEFAULTS中取得了authentication_classes的值

最終APIView中authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES的執行結果是

authentication_classes =
     (
    SessionAuthentication,
    BasicAuthentication
    ),

於是

authenticators = [SessionAuthentication(),BasicAuthentication()]

最終在 _authenticate原始碼中執行的是SessionAuthentication,BasicAuthentication這兩個方法中的authenticate(self, request)方法

class SessionAuthentication(BaseAuthentication):
    """
    Use Django's session framework for authentication.
    """

    def authenticate(self, request):
        """
        Returns a `User` if the request session currently has a logged in user.
        Otherwise returns `None`.
        """

        # Get the session-based user from the underlying HttpRequest object
        user = getattr(request._request, 'user', None)

        # Unauthenticated, CSRF validation not required
        if not user or not user.is_active:
            return None

        self.enforce_csrf(request)

        # CSRF passed with authenticated user
        return (user, None)

    def enforce_csrf(self, request):
        """
        Enforce CSRF validation for session based authentication.
        """
        reason = CSRFCheck().process_view(request, None, (), {})
        if reason:
            # CSRF failed, bail with explicit error message
            raise exceptions.PermissionDenied('CSRF Failed: %s' % reason)

 

authenticate方法的邏輯就是就是認證元件的實際邏輯
根據整個原始碼的思路,可以在重新寫一個認證類,而其中必定有authenticate方法來控制驗證邏輯
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.authentication import BaseAuthentication

class TokenAuth(BaseAuthentication):

    def authenticate(self,request):

        token=request.GET.get("token",None)

        token_obj=UserToken.objects.filter(token=token).first()
        if token_obj:
            return token_obj.user.user,token_obj
        else:
            raise AuthenticationFailed("認證失敗!")

 

class BookView(ModelViewSet):
    # 指定認證類
    authentication_classes = [TokenAuth]
    queryset = Book.objects.all()
    serializer_class = BookSerializer

 三、許可權元件原始碼剖析

許可權元件大部分與認證元件執行流程相似,在下面僅列出不同之處,原始碼如下:

    def initial(self, request, *args, **kwargs):
        #認證元件
        self.perform_authentication(request)
        #許可權元件
        self.check_permissions(request)
        #頻率元件
        self.check_throttles(request)
def check_permissions(self, request):
        """
        Check if the request should be permitted.
        Raises an appropriate exception if the request is not permitted.
        """
        for permission in self.get_permissions():      #self.get_permissions() = permission_classes
        if not permission.has_permission(request, self): 
          self.permission_denied( request, message
=getattr(permission, 'message', None) )
根據整個原始碼的思路,可以在重新寫一個許可權類,而其中必定有has_permission方法來控制驗證邏輯
class BookView(ModelViewSet):
    # 指定認證類
    authentication_classes = [UserAuth]
    permission_classes = [UserPerm]
    throttle_classes = [MyThrottle]
    queryset = Book.objects.all()
    serializer_class = BookSerializer
class UserPerm():
    message = "您沒有檢視該資料的許可權!"

    def has_permission(self, request, view):
        if request.user.user_type == 3:
            return True
        return False

 

一:使用RestFramwork,定義一個檢視

from rest_framework.viewsets import ModelViewSet


class BookView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

認證、頻率和許可權元件都由繼承的ModelViewSet支援,所以要了解這三個元件的具體如何實現

對認證、頻率、許可權的管理就需要進入到其中檢視

二:首先來了解元件認證

由上圖可以看到ModelViewSet繼承了六個類,前面的五個並沒有元件的內容,不去管,接下來進入GenericViewSet類中看看

GenericViewSet這個類沒有具體的程式碼,但是可以看到它繼承了兩個類ViewSetMixin,和generics.GenericAPIView

ViewSetMixin

這個類中主要需要了解的是as_view這個在url中使用的方法,這個類只是重寫了as_view中的view方法,具體的核心程式碼如下

for method, action in actions.items():
    handler = getattr(self, action)
    setattr(self, method, handler)

簡單來說,就是把url中傳入的字典for迴圈,利用反射找到對應的方法重新設定get請求對應的函式

url(r'^authors/$', views.AuthorModelView.as_view({"get":"list","post":"create"})),

如上:在Django啟動後,views.AuthorModelView.as_view({"get":"list","post":"create"})的執行結果是一個閉包函式view

請求傳送進來,根據閉包函式外的actions:{"get":"list","post":"create"}設定self.get = list或者設定 self.post= create等等

由上可知,這個函式也與要找的元件關係不大。

generics.GenericAPIView

def get_queryset(self):
def get_object(self):
def get_serializer(self, *args, **kwargs):
def get_serializer_class(self):
def get_serializer_context(self):
def filter_queryset(self, queryset):
@property
def paginator(self):
def paginate_queryset(self, queryset):
def get_paginated_response(self, data):

類中方法與要找元件無關,繼續進入其父類中找

在父類APIView中的dispach方法中

self.initial(request, *args, **kwargs)這一段程式碼負責所有的認證、許可權和頻率管理

因為檢視的繼承複雜,現在需要搞清楚類的繼承關係和程式碼具體執行步驟,才好往下走

繼承關係圖

請求執行流程

Django啟動

url(r'^authors/$', views.AuthorModelView.as_view({"get":"list","post":"create"})) 在Django啟動時就執行,as_view的執行結果是一個閉包函式

view,由actions = {"get":"list","post":"create"}等引數包裹:

實際路由為:url(r'^authors/$', view)

請求到來:

根據繼承關係:請求到來執行的view函式是類ViewSetMixin中的閉包函式view

view原始碼

def view(request, *args, **kwargs):
    self = cls(**initkwargs)
    # We also store the mapping of request methods to actions,
    # so that we can later set the action attribute.
    # eg. `self.action = 'list'` on an incoming GET request.
    self.action_map = actions

    # Bind methods to actions
    # This is the bit that's different to a standard view
    for method, action in actions.items():
        handler = getattr(self, action)
        setattr(self, method, handler)

    if hasattr(self, 'get') and not hasattr(self, 'head'):
        self.head = self.get

    self.request = request
    self.args = args
    self.kwargs = kwargs

    # And continue as usual
    return self.dispatch(request, *args, **kwargs)

可以看到,在將self.get,self.post等方法對映之後,view方法最終返回了self.dispatch(request, *args, **kwargs)的執行結果

根據物件的屬性和方法查詢原則,self.dispatchfan方法呼叫的是類APIView中的dispatch方法

dispatch原始碼

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

dispatch的核心功能就是根據請求的方法不同,分發執行不同的程式碼,並最終返回結果。

在這裡我注意到,每次請求分發之前都會執行self.initial(request, *args, **kwargs) 這段程式碼,也就是說每次請求都會進入這裡執行。

initial原始碼

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)  # 頻率

initial中的核心程式碼是依次執行:

self.perform_authentication(request) # 認證

self.check_permissions(request) # 許可權

self.check_throttles(request) # 頻率

也就是:認證通過才會驗證許可權,許可權驗證通過才會驗證頻率

perform_authentication原始碼

def perform_authentication(self, request):
    request.user

perform_authentication中返回了request.user,首先要明白這個request來自於哪裡?

從dispatch中一路過來,request一直沒做處理,說明request至少來自於dispatch,APIView中dispatch的原始碼中有一行程式碼可以解釋request的來源

request = self.initialize_request(request, *args, **kwargs)
self.request = request

initialize_request原始碼

def initialize_request(self, request, *args, **kwargs):
    """
    Returns the initial request object.
    """
    parser_context = self.get_parser_context(request)

    return Request(
        request,
        parsers=self.get_parsers(),
        authenticators=self.get_authenticators(),
        negotiator=self.get_content_negotiator(),
        parser_context=parser_context
    )

 initialize_request程式碼中返回了一個Request類的物件,傳入了

request
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context

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

@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()
    return self._user

 

perform_authentication程式碼中執行的request.user就是執行的Request類的user方法

user方法中的程式碼程式碼表示如果沒有_user屬性就執行self._authenticate()

 _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:
                self._not_authenticated()
                raise

            if user_auth_tuple is not None:
                self._authenticator = authenticator
                self.user, self.auth = user_auth_tuple
                return

        self._not_authenticated()

 _authenticate:for迴圈self.authenticators並賦值給authenticator,然後執行authenticate方法

 首先要知道self.authenticators來自於哪裡?

回溯程式碼:

_authenticate中呼叫了self.authenticators。

self物件來自於user方法

user方法中的self物件Request的例項化物件

Request的例項化物件的例項化物件有一個屬性:

self.authenticators= authenticators or ()

authenticators 是一個Request類的例項化引數,預設為None,如果有傳入引數則為傳入的值

 在initialize_request原始碼中例項化時:authenticators=self.get_authenticators(),

return Request(
    request,
    parsers=self.get_parsers(),
    authenticators=self.get_authenticators(),
    negotiator=self.get_content_negotiator(),
    parser_context=parser_context
)

這時的self來自於呼叫initialize_request的物件

initialize_request在dispatch中被呼叫,dispatch的呼叫物件即是自定義的檢視類的例項化物件

也即使說self.get_authenticators()是檢視類呼叫的get_authenticators方法

 get_authenticators原始碼

def get_authenticators(self):

    return [auth() for auth in self.authentication_classes]

 get_authenticators中for迴圈檢視類的authentication_classes的屬性,加括號例項化組成一個列表返回

於是查詢物件的屬性,首先從物件自己找,然後從檢視類中找,如果找不到,在依照繼承關係從被繼承的類中找

在被檢視類所繼承的類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
    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

    # Allow dependency injection of other settings to make testing easier.
    settings = api_settings
api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)
APISettings類中並沒有DEFAULT_AUTHENTICATION_CLASSES屬性,自動觸發__getattr__方法
APISettings原始碼
class APISettings(object):

    def __init__(self, user_settings=None, defaults=None, import_strings=None):
        if user_settings:  # 如果user_settings有值執行下列程式碼
            self._user_settings = self.__check_user_settings(user_settings)
        self.defaults = defaults or DEFAULTS
        # defaults有值則賦給self.defaults,沒有則把DEFAULTS賦值給self.defaults
        self.import_strings = import_strings or IMPORT_STRINGS
        self._cached_attrs = set()

    @property
    def user_settings(self):
        if not hasattr(self, '_user_settings'): # 如果_user_settings沒有定義
            self._user_settings = getattr(settings, 'REST_FRAMEWORK', {})
            # 從Django專案的settings檔案中利用反射取出'REST_FRAMEWORK'的值賦給self._user_settings
        return self._user_settings

    def __getattr__(self, attr):  # 物件用.attr的方法查詢不到屬性時自動觸發
        if attr not in self.defaults: # 如果self.defaults中沒有查詢的屬性則報錯
            raise AttributeError("Invalid API setting: '%s'" % attr)

        try:
            # Check if present in user settings
            val = self.user_settings[attr]
            # 從self.user_settings執行返回的值中取出屬性attr的的值賦給val
        except KeyError:
            # Fall back to defaults
            val = self.defaults[attr]

        # Coerce import strings into classes
        if attr in self.import_strings:
            # 如果屬性attr在self.import_strings中通過反射取出對應的相應的方法或屬性做進一步處理
            val = perform_import(val, attr)

        # Cache the result
        self._cached_attrs.add(attr)
        setattr(self, attr, val) # 利用反射給檢視類物件設定一個屬性attr值為val
        return val
DEFAULTS = {
    # Base API policies
    'DEFAULT_RENDERER_CLASSES': (
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ),
    'DEFAULT_PARSER_CLASSES': (
        'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser'
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication'
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.AllowAny',
    ),
    'DEFAULT_THROTTLE_CLASSES': (),
    'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'rest_framework.negotiation.DefaultContentNegotiation',
    'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata',
    'DEFAULT_VERSIONING_CLASS': None,

    # Generic view behavior
    'DEFAULT_PAGINATION_CLASS': None,
    'DEFAULT_FILTER_BACKENDS': (),

    # Schema
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema',

    # Throttling
    'DEFAULT_THROTTLE_RATES': {
        'user': None,
        'anon': None,
    },
    'NUM_PROXIES': None,

    # Pagination
    'PAGE_SIZE': None,

    # Filtering
    'SEARCH_PARAM': 'search',
    'ORDERING_PARAM': 'ordering',

    # Versioning
    'DEFAULT_VERSION': None,
    'ALLOWED_VERSIONS': None,
    'VERSION_PARAM': 'version',

    # Authentication
    'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser',
    'UNAUTHENTICATED_TOKEN': None,

    # View configuration
    'VIEW_NAME_FUNCTION': 'rest_framework.views.get_view_name',
    'VIEW_DESCRIPTION_FUNCTION': 'rest_framework.views.get_view_description',

    # Exception handling
    'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
    'NON_FIELD_ERRORS_KEY': 'non_field_errors',

    # Testing
    'TEST_REQUEST_RENDERER_CLASSES': (
        'rest_framework.renderers.MultiPartRenderer',
        'rest_framework.renderers.JSONRenderer'
    ),
    'TEST_REQUEST_DEFAULT_FORMAT': 'multipart',

    # Hyperlink settings
    'URL_FORMAT_OVERRIDE': 'format',
    'FORMAT_SUFFIX_KWARG': 'format',
    'URL_FIELD_NAME': 'url',

    # Input and output formats
    'DATE_FORMAT': ISO_8601,
    'DATE_INPUT_FORMATS': (ISO_8601,),

    'DATETIME_FORMAT': ISO_8601,
    'DATETIME_INPUT_FORMATS': (ISO_8601,),

    'TIME_FORMAT': ISO_8601,
    'TIME_INPUT_FORMATS': (ISO_8601,),

    # Encoding
    'UNICODE_JSON': True,
    'COMPACT_JSON': True,
    'STRICT_JSON': True,
    'COERCE_DECIMAL_TO_STRING': True,
    'UPLOADED_FILES_USE_URL': True,

    # Browseable API
    'HTML_SELECT_CUTOFF': 1000,
    'HTML_SELECT_CUTOFF_TEXT': "More than {count} items...",

    # Schemas
    'SCHEMA_COERCE_PATH_PK': True,
    'SCHEMA_COERCE_METHOD_NAMES': {
        'retrieve': 'read',
        'destroy': 'delete'
    },
}

DEFAULTS
DEFAULT

在本例中檢視類中並沒有重寫authentication_classes,因此根據APISettings中的程式碼可知,程式首先在Django的settings檔案中查詢,由於settins檔案中沒有定義,因此丟擲異常,最終從DEFAULTS中取得了authentication_classes的值

最終APIView中authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES的執行結果是

authentication_classes =
     (
    SessionAuthentication,
    BasicAuthentication
    ),

於是

authenticators = [SessionAuthentication(),BasicAuthentication()]

最終在 _authenticate原始碼中執行的是SessionAuthentication,BasicAuthentication這兩個方法中的authenticate(self, request)方法

class SessionAuthentication(BaseAuthentication):
    """
    Use Django's session framework for authentication.
    """

    def authenticate(self, request):
        """
        Returns a `User` if the request session currently has a logged in user.
        Otherwise returns `None`.
        """

        # Get the session-based user from the underlying HttpRequest object
        user = getattr(request._request, 'user', None)

        # Unauthenticated, CSRF validation not required
        if not user or not user.is_active:
            return None

        self.enforce_csrf(request)

        # CSRF passed with authenticated user
        return (user, None)

    def enforce_csrf(self, request):
        """
        Enforce CSRF validation for session based authentication.
        """
        reason = CSRFCheck().process_view(request, None, (), {})
        if reason:
            # CSRF failed, bail with explicit error message
            raise exceptions.PermissionDenied('CSRF Failed: %s' % reason)

 

authenticate方法的邏輯就是就是認證元件的實際邏輯
根據整個原始碼的思路,可以在重新寫一個認證類,而其中必定有authenticate方法來控制驗證邏輯
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.authentication import BaseAuthentication

class TokenAuth(BaseAuthentication):

    def authenticate(self,request):

        token=request.GET.get("token",None)

        token_obj=UserToken.objects.filter(token=token).first()
        if token_obj:
            return token_obj.user.user,token_obj
        else:
            raise AuthenticationFailed("認證失敗!")

 

class BookView(ModelViewSet):
    # 指定認證類
    authentication_classes = [TokenAuth]
    queryset = Book.objects.all()
    serializer_class = BookSerializer

 三、許可權元件原始碼剖析

許可權元件大部分與認證元件執行流程相似,在下面僅列出不同之處,原始碼如下:

    def initial(self, request, *args, **kwargs):
        #認證元件
        self.perform_authentication(request)
        #許可權元件
        self.check_permissions(request)