一:使用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
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
在本例中视图类中并没有重写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