【1127 | Day67】认证模块的全局配置(源码分析)

我们再走一步流程

1、进入urls.py路由文件

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进行初始化

img

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]

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

img

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

img

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)

img

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方法执行的结果

img

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这个方法

img

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

img

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

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)

img

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

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

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

img

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

img

然后看user_settings这个方法

img

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

img

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

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

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

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

img

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

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

img

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

img

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

我们模仿上面写我们的REST_FRAMEWORK img

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

28、我们使用postman进行测试

先使用get的方式访问book_cbv img

img

然后使用post方式访问book_cbv img

最后使用post访问book_detail_cbv img

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

至此Rest_framework全局设置认证组件的源码剖析和实现我们都讲完了。

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

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

img

posted @ 2019-11-27 14:00  fxyadela  阅读(232)  评论(0编辑  收藏  举报