zoukankan      html  css  js  c++  java
  • 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

    在本例中视图类中并没有重写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("认证失败!")
    
    
  • 相关阅读:
    欧拉图
    hdu2544 迪杰斯特拉题目优化
    迪杰斯特拉--数组模拟邻接表优化
    快速幂
    四叉树 bnuoj
    逆康拓展开展开
    全排列 STL
    魔板拼图
    「luogu4366」最短路
    「国家集训队」稳定婚姻
  • 原文地址:https://www.cnblogs.com/li1992/p/9416156.html
Copyright © 2011-2022 走看看