Django的rest_framework认证组件之全局设置源码解析

前言:

  在我的上一篇博客我介绍了一下单独为某条url设置认证,但是如果我们想对所有的url设置认证,该怎么做呢?我们这篇博客就是给大家介绍一下在Rest_framework中如何实现全局的设置认证组件的功能。下面就请大家跟着我的思路看博客

  如果有对局部设置不清楚的,可以看我的上一篇博客,源码级的分析单独设置Rest_framework的认证组件:https://www.cnblogs.com/bainianminguo/p/10480887.html

正文:

我们在走一步流程

1、进入urls路由文件

url(r'^login/', views.LoginCBV.as_view(),name="login"),

  

2、进入as_view这个方法,这个方法被类直接调用,那么这个方法一定会被classmethod修饰符修饰,是一个类方法

    @classmethod
    def as_view(cls, **initkwargs):
        """
        Store the original class on the view function.

        This allows us to discover information about the view when we do URL
        reverse lookups.  Used for breadcrumb generation.
        """
        if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet):
            def force_evaluation():
                raise RuntimeError(
                    'Do not evaluate the `.queryset` attribute directly, '
                    'as the result will be cached and reused between requests. '
                    'Use `.all()` or call `.get_queryset()` instead.'
                )
            cls.queryset._fetch_all = force_evaluation

        view = super(APIView, cls).as_view(**initkwargs)
        view.cls = cls
        view.initkwargs = initkwargs

        # Note: session based authentication is explicitly CSRF validated,
        # all other authentication is CSRF exempt.
        return csrf_exempt(view)

  

3、as_view这个方法,我们看到返回值为view的方法的返回值,而view这个方法又是什么?我们在as_view方法中看到这样一段代码,就是执行父类的as_view的方法

        view = super(APIView, cls).as_view(**initkwargs)

  

4、进入APIView的父类的as_view方法,也就是View类的as_view方法

    @classonlymethod
    def as_view(cls, **initkwargs):
        """
        Main entry point for a request-response process.
        """
        for key in initkwargs:
            if key in cls.http_method_names:
                raise TypeError("You tried to pass in the %s method name as a "
                                "keyword argument to %s(). Don't do that."
                                % (key, cls.__name__))
            if not hasattr(cls, key):
                raise TypeError("%s() received an invalid keyword %r. as_view "
                                "only accepts arguments that are already "
                                "attributes of the class." % (cls.__name__, key))

        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get
            self.request = request
            self.args = args
            self.kwargs = kwargs
            return self.dispatch(request, *args, **kwargs)
        view.view_class = cls
        view.view_initkwargs = initkwargs

        # take name and docstring from class
        update_wrapper(view, cls, updated=())

        # and possible attributes set by decorators
        # like csrf_exempt from dispatch
        update_wrapper(view, cls.dispatch, assigned=())
        return view

  

5、下面重点来分析View类的as_view方法,这个as_view方法返回是一个view方法的执行的结果,而view方法又干了什么,我们看下view方法,这个方法返回的是dispatch方法

        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get
            self.request = request
            self.args = args
            self.kwargs = kwargs
            return self.dispatch(request, *args, **kwargs)

  

6、下面我们看下dispatch方法,这个dispatch方法是APIView这个类的方法

    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

  

7、先看这里,将源生的request进行初始化

 8、看下initialize_request方法,这个方法返回了一个新的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
        )

  

 9、因为我们这里在做认证的源码分析,我们重点看下authenticators这个属性的,也就是get_authenticators方法,这里要非常的注意,这里非常的关键,就是有self.authentication_classes这个属性

    def get_authenticators(self):
        """
        Instantiates and returns the list of authenticators that this view can use.
        """
        return [auth() for auth in self.authentication_classes]

  

重点是这个属性,大家一定要记住

 10、下面我们接着步骤7在往后执行,看下initial这个方法

 11、进入这个initial这个方法,这里有3个组件,认证,权限,频率,我们重点看认证这个组件

    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)

  

12、看下认证组件的方法perform_authentication这个方法,返回一个request.user这个,request是什么,我们看到在执行initial方法的时候,传了一个request进去,这个request就是request.user的这个request

    def perform_authentication(self, request):
        """
        Perform authentication on the incoming request.

        Note that if you override this and simply 'pass', then authentication
        will instead be performed lazily, the first time either
        `request.user` or `request.auth` is accessed.
        """
        request.user

  

 13、我们在汇过去看下inital方法传递的参数request,我们看到initial方法的request是initalize_request方法执行的结果

 14、下面看下initalize_request这个方法返回的是什么?返回了一个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
        )

  

 15、进入Request类,我们看下的user属性或者方法,看代码,发现这是一个被propery修饰过的方法,调用这个方法的方法和调用属性的方法一样

    @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

  

16、Request这个类,我们看了下没有_user这个属性,所以会进入if的条件语句,下面我们看下_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()

  

17、这个方法有些参数大家可能也不清楚,我们在回答一下,先看下authenticators,由于这个self是Request类的一个实例对象

for authenticator in self.authenticators:

  

我们看下实例化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
        )

  

我们很清楚的可以看到authenticators这个值就是get_authenticators这个方法的返回值,我们在看这个方法

    def get_authenticators(self):
        """
        Instantiates and returns the list of authenticators that this view can use.
        """
        return [auth() for auth in self.authentication_classe

  

authentication_classes就是我们自己的配置的认证类

18、在看下authenticator.authenticate这个方法

 19、然后这个时候就可以看下面我们的认证配置

首先定义了一个认证类,这个认证类有authenticate方法,这个方法的返回值为一个元组,我已经圈起来来

class Book_auther(BaseAuthentication):
    def authenticate(self,request):
        token = request.GET.get("token")
        token_obj = models.Token.objects.filter(token=token).first()
        if token_obj:
            return token_obj.user.name,token_obj.token
        else:
            raise exceptions.AuthenticationFailed("验证失败")
    def authenticate_header(self,request):
        pass

  

 

 然后在我们自己的视图类中定义了认证的类的列表,实例化我们的认证类

class Book_cbv(APIView):
    authentication_classes = [Book_auther,]
    def get(self,request):
        query_list = models.Book.objects.all()
        # bs = book_serializers(query_list,many=True)
        bs = bookmodelserializer(query_list,many=True,context={'request': request})


        return Response(bs.data)
    def post(self,request):
        bs = bookmodelserializer(data=request.data)
        print(request.data)
        if bs.is_valid():
            print(bs.validated_data)
            bs.save()
            return Response(bs.data)
        else:
            return Response(bs.errors)

  

 20、这个时候,我们才能正式进入认证类的全局配置的地方,做全局配置,我们当然不能在每个视图类中配置,那么如果我们不配置这个authentication_classes这个属性呢?

其实APIView默认是有这个参数,如果我们没有配置,则用APIView这个类的属性

 21、看到这句代码大家可能不懂,如果看不懂,大家可以下我的这篇博客,介绍面向对象的__getattr__方法的作用

博客地址:https://www.cnblogs.com/bainianminguo/p/10475204.html

authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES

  

 22、下面我们进入api_settings这个实例,我们看到api_settings这个是APISettings类的实例对象

api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)

  

23、然后我们在看下APISettings这个类

class APISettings(object):
    """
    A settings object, that allows API settings to be accessed as properties.
    For example:

        from rest_framework.settings import api_settings
        print(api_settings.DEFAULT_RENDERER_CLASSES)

    Any setting with string import paths will be automatically resolved
    and return the class, rather than the string literal.
    """
    def __init__(self, user_settings=None, defaults=None, import_strings=None):
        if user_settings:
            self._user_settings = self.__check_user_settings(user_settings)
        self.defaults = defaults or DEFAULTS
        self.import_strings = import_strings or IMPORT_STRINGS
        self._cached_attrs = set()

  

24、看了我的博客,就会知道__getattr__这个方法的使用场景,也就知道下面这段代码实际就会执行APISettings类的__getattr__方法

    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES

  

25、下面我们看下APISettings类的__getattr__方法

    def __getattr__(self, attr):
        if attr not in self.defaults:
            raise AttributeError("Invalid API setting: '%s'" % attr)

        try:
            # Check if present in user settings
            val = self.user_settings[attr]
        except KeyError:
            # Fall back to defaults
            val = self.defaults[attr]

        # Coerce import strings into classes
        if attr in self.import_strings:
            val = perform_import(val, attr)

        # Cache the result
        self._cached_attrs.add(attr)
        setattr(self, attr, val)
        return val

  

在看下user_settings是是否有DEFAULT_AUTHENTICATION_CLASSES这个k值

 

 然后看user_settings这个方法

下面就是在实例化APISettings类的时候代码,第一个参数user_settings,这个参数的值None

 

 所以APISettings这个类的实例对象没有_user_settings这个属性,所以会进入if的流程中

 这个settings是什么呢?其实就是Djaong的project的settings文件

 所以我们就需要在settings中配置“REST_FRAMEWORK”这个属性的值为一个字典,我们看到后面如果拿不到“REST_FRAMEWORK”就会给赋值给空的字典

 26、但是到了这里,我们字典该怎么写呢?大家一脸的懵逼了,我们可以看下这段代码,如果我们在settings没有配置“REST_FRAMEWORK”就会走下面的流程,我们看下面的流程的是什么东西

 27、self.defaults是什么,就是我们在实例时候APISetings这个类的时候传递的参数

 在来回忆一下,实例化APISettings类

在看下初始化APISettings类的时候__init__方法

 我们看下DEFAULTS是什么,就是下面的配置

 我们模仿上面写我们的REST_FRAMEWORK

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES":(
        "app1.utils.Book_auther",
    )
}

  

 28、我们使用postman进行测试

先使用get的方式访问book_cbv

然后使用post方式访问book_cbv

 最后使用post访问book_detail_cbv

 最后我们加上token在访问一次,访问了2个url,均可以访问成功

 

 至此Rest_framework全局设置认证组件的源码剖析和实现我们多讲完了,大家请查看,并给出意见

这里补充一个知识点,我们现在已经配置全局认证,那么如果有些url,比如登陆,就不能让他走认证,我们该怎么办?

其实很简单,为什么会走到全局配置的这里,就是因为单独的视图类中没有配置认证相关的东西,如果为这个视图类配置认证的组件,那么他就 不会走全局的配置,会走自己的私有的配置,我们为这个私有的配置设置一个空的列表就可以了

原文地址:https://www.cnblogs.com/bainianminguo/p/10487059.html