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' }, } DEFAULTSDEFAULT
在本例中檢視類中並沒有重寫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' }, } DEFAULTSDEFAULT
在本例中檢視類中並沒有重寫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)