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("认证失败!")
    
    
  • 相关阅读:
    PAT 1006 Sign In and Sign Out
    PAT 1004. Counting Leaves
    JavaEE开发环境安装
    NoSql数据库探讨
    maven的配置
    VMWARE 下使用 32位 Ubuntu Linux ,不能给它分配超过3.5G 内存?
    XCODE 4.3 WITH NO GCC?
    在苹果虚拟机上跑 ROR —— Ruby on Rails On Vmware OSX 10.7.3
    推荐一首让人疯狂的好歌《Pumped Up Kicks》。好吧,顺便测下博客园可以写点无关技术的帖子吗?
    RUBY元编程学习之”编写你的第一种领域专属语言“
  • 原文地址:https://www.cnblogs.com/li1992/p/9416156.html
Copyright © 2011-2022 走看看