一:使用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

Rest_Framework之认证、权限、频率组件源码剖析的更多相关文章

  1. Element 2 组件源码剖析之布局容器

    0x00 简介 前文分析过组件的 布局栅格化(Grid Layout) ,通过基础的 24 分栏,迅速简便地创建布局. 本文将介绍用于布局的容器组件,使用 Flexbox 功能将其所控制区域设定为特定 ...

  2. Django的rest_framework的权限组件和频率组件源码分析

    前言: Django的rest_framework一共有三大组件,分别为认证组件:perform_authentication,权限组件:check_permissions,频率组件:check_th ...

  3. Django框架之DRF 认证组件源码分析、权限组件源码分析、频率组件源码分析

    认证组件 权限组件 频率组件

  4. Django REST framework认证权限和限制 源码分析

    1.首先 我们进入这个initial()里面看下他内部是怎么实现的. 2.我们进入里面看到他实现了3个方法,一个认证,权限频率 3.我们首先看下认证组件发生了什么 权限: 啥都没返回,self.per ...

  5. DRF-认证 权限 频率组件

    补充 1 认证 权限 频率组件原理基本相同 2 认证相关: session cookie token 认证相关的  这里用token token 1 有时间限制,超时则失效 2 每次登录更换一个tok ...

  6. 06 drf源码剖析之权限

    06 drf源码剖析之权限 目录 06 drf源码剖析之权限 1. 权限简述 2. 权限使用 3.源码剖析 4. 总结 1. 权限简述 权限与身份验证和限制一起,决定了是否应授予请求访问权限. 权限检 ...

  7. 05 drf源码剖析之认证

    05 drf源码剖析之认证 目录 05 drf源码剖析之认证 1. 认证简述 2. 认证的使用 3. 源码剖析 4. 总结 1. 认证简述 当我们通过Web浏览器与API进行交互时,我们可以登录,然后 ...

  8. drf源码剖析系列(系列目录)

    drf源码剖析系列(系列目录) 01 drf源码剖析之restful规范 02 drf源码剖析之快速了解drf 03 drf源码剖析之视图 04 drf源码剖析之版本 05 drf源码剖析之认证 06 ...

  9. Django-restframework 源码之认证组件源码分析

    Django-restframework 源码之认证组件源码分析 一 前言 之前在 Django-restframework 的流程分析博客中,把最重要的关于认证.权限和频率的方法找到了.该方法是 A ...

随机推荐

  1. TensorFlow2.0(三):排序及最大、最小、平均值

    .caret, .dropup > .btn > .caret { border-top-color: #000 !important; } .label { border: 1px so ...

  2. Ubuntu使用vi命令时,不能正常编辑文件,使用方向键时老是出现很多字母解决方案

    原因是系统只装了vi,没有装vim.因为vi是不能直接按退格键删除字符的.所以重新装下vim指令即可: # sudo apt-get install vim 重新使用vi命令进行文件编辑.

  3. scalikejdbc 学习笔记(5)

    常用增删改查操作: import scalikejdbc._ import scalikejdbc.config._ object CommonOperation { def main(args: A ...

  4. 【Java】获取当前操作系统桌面路径

    //当前用户桌面 File desktopDir = FileSystemView.getFileSystemView() .getHomeDirectory(); String desktopPat ...

  5. redis系列之------字典

    前言 字典, 又称符号表(symbol table).关联数组(associative array)或者映射(map), 是一种用于保存键值对(key-value pair)的抽象数据结构. 在字典中 ...

  6. 低效sql语句执行缓慢引起的大量占用服务器的CPU问题处理 (优化心得)

    1> 2> 3> 4> 5>删除不良的执行计划后执行时间仍然有150s,这实在是太慢了,继续查看原sql代码,发现父表的关联条件放在了子查询里,这是应该避免的 调整原sq ...

  7. ORM查询总结版

    目录 概要 ORM常用字段 ORM基础 自定义一个插入类型,即固定长度 创建类终极版 多对多关系表创建 常用几个代码 参数 ORM与数据库代码对应的关系 外键使用分表很麻烦,要先删除主表后,再删除 不 ...

  8. .net Core 发布服务

    .net core 发布服务 准备好的文件可以通过下面的几个命令进行操作 1.创建Service sc create "服务名" binPath= "文件路径+文件名&q ...

  9. go语言正则表达式

    我们前两节课爬取珍爱网的时候,用到了很多正则表达式去匹配城市列表.城市.用户信息,其实除了正则表达式去匹配,还可以利用goquery和xpath第三方库匹配有用信息.而我利用了更优雅的正则表达式匹配. ...

  10. docker的使用之镜像命令

    说明 Docker运行容器前需要本地存在对应的镜像 ,如果镜像不存在本地,Docker会从镜像仓库下载 获取镜像 通过网址可以找到目标镜像 https://hub.docker.com/explore ...