DRF学习——认证组件(一)

本文介绍了如何在DRF中自定义认证组件,避免循环引用问题,并详细分析了认证组件的调用过程,包括从request初始化、认证类的获取到认证过程的执行,以及认证失败的处理。同时,展示了在views中如何设置和使用认证类,以及在不同视图中控制认证需求的示例。
摘要由CSDN通过智能技术生成

一、简介

本篇文章主要介绍drf认证组件的快速使用,并从源码角度分析drf认证组件的调用过程

二、快速使用

①首先自定义一个认证组件auth.py,该组件可以放在项目的任何位置,除了views.py中,如果写在views.py中会出现循环引用的问题,具体原因如下,全局的认证组件放在了settings.py中

REST_FRAMEWORK={
    'UNAUTHENTICATED_USER': None,
    'DEFAULT_AUTHENTICATION_CLASSES': ['自定义认证组件的路径', ],
}

如由于views.py继承了APIView类,而在APIView类中定义了 authentication_classes

authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES

当读取settings.py中的自定义组件路径时,便会加载views.py,从而通过APIView读取到上面这段代码,又会跳到settings.py中读取'DEFAULT_AUTHENTICATION_CLASSES',如此反复导致了循环引用,因此需要将自定义认证组件放在除view.py的任何位置即可

我创建了一个ext目录,将auth.py写在了ext目录下

 因此settings.py的路径如下:

REST_FRAMEWORK={
    'UNAUTHENTICATED_USER': None,
    'DEFAULT_AUTHENTICATION_CLASSES': ['ext.auth.MyAuthentication', ],
}

auth.py代码如下:

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed


class MyAuthentication(BaseAuthentication):
    def authenticate(self, request):
        # 用户认证步骤:
        # 1.读取请求传递的token
        # 2.校验合法性
        # 3.三种返回值
        #   3.1 认证成功,返回元组(11,22)     
        #   11,22分别存入request.user和request.auth
        #   3.2 认证失败,抛出异常   返回错误信息
        #   3.3 返回None(不常用),用于多个认证类

        # /xxx/xxx/?token=1231dd
        # token = request._request.GET.get('token')
        # drf的request源码中的query_params=_request.GET
        token = request.query_params.get('token')
        # 这里获取到请求传递的token后,理应校验合法性,不过这里先略去
        if token:
            return "user", token
        # raise AuthenticationFailed("认证失败")
        # 如果返回字符串,字符串会放入detail中,如果返回字典,则该字典会代替detail
        raise AuthenticationFailed({"code": 0, "msg": "认证失败"})

②在不需要认证的类中定义authentication_classes为空

views.py代码

from rest_framework.views import APIView
from rest_framework.response import Response


class UserView(APIView):

    def get(self, request):
        print(request.user)
        print(request.auth)
        return Response("UserView")


class LoginView(APIView):
    # LoginView不需要认证
    authentication_classes = []

    def get(self, request):
        print(request.user)
        print(request.auth)
        return Response("LoginView")


class OrderView(APIView):

    def get(self, request):
        self.dispatch()
        return Response("OrderView")

③访问url测试api接口

访问login不需要加token

访问user如果不加token便会报错

如果加上token便可正常访问

 

 三、源码分析

接下来介绍drf的认证组件源码,解析其是如何实现认证功能的

通过上上节CBV和FBV的学习后,我们知道实现了不同请求方式的访问是通过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

首先查看这段代码

request = self.initialize_request(request, *args, **kwargs)

大致意思是通过调用initialize_request方法将django的request封装成drf的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
        )

返回了一个Request对象,其中authenticators=self.get_authenticators(),那么就需要看一下get_authenticators做了什么事,其源码如下:

    def get_authenticators(self):
        return [auth() for auth in self.authentication_classes]

返回的是一个装着对象的列表,即[对象,对象,对象],而这实例化对象读取的是self.authentication_classes,这里的self是views.py中的继承APIView的类,如LoginView,UserView等,因此首先会去类本身里找authentication_classes,如LoginView代码定义了authentication_classes为空

authentication_classes = []

如果没找到便去父类APIView中找authentication_classes,而上面提到APIView中的authentication_classes定义如下:

authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES

因此即是去settings中寻找api_settings.DEFAULT_AUTHENTICATION_CLASSES的定义,即

'DEFAULT_AUTHENTICATION_CLASSES': ['ext.auth.MyAuthentication', ]

现在已经明白了,传入Request对象中的authenticators参数是什么,那么接下来去看一下Request对象是如何定义的,其源码(仅__init__部分)如下:

class Request:
    """
    Wrapper allowing to enhance a standard `HttpRequest` instance.

    Kwargs:
        - request(HttpRequest). The original request instance.
        - parsers(list/tuple). The parsers to use for parsing the
          request content.
        - authenticators(list/tuple). The authenticators used to try
          authenticating the request's user.
    """

    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
        self.parsers = parsers or ()
        self.authenticators = authenticators or ()
        self.negotiator = negotiator or self._default_negotiator()
        self.parser_context = parser_context
        self._data = Empty
        self._files = Empty
        self._full_data = Empty
        self._content_type = Empty
        self._stream = Empty

        if self.parser_context is None:
            self.parser_context = {}
        self.parser_context['request'] = self
        self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET

        force_user = getattr(request, '_force_auth_user', None)
        force_token = getattr(request, '_force_auth_token', None)
        if force_user is not None or force_token is not None:
            forced_auth = ForcedAuthentication(force_user, force_token)
            self.authenticators = (forced_auth,)

我们只需要关注如下这句代码:

 self.authenticators = authenticators or ()

将authenticators 即[对象,对象,对象]赋值给了self.authenticators,即request.authenticators中存储的便是[对象,对象,对象]

接下来回到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

走到如下代码

self.initial(request, *args, **kwargs)

调用了initial方法,那么就需要知道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)

我们需要关注这段代码

self.perform_authentication(request)

这段代码调用了perform_authentication方法,那么就需要知道perform_authentication做了什么,其源码如下:

    def perform_authentication(self, request):
        request.user

这里返回了一个request.user,我们已经知道此时的request已经是drf的request,request来自于Request类,那么我们需要去Request类中寻找user的定义,其源码如下:

@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

@property的作用即是当调用该方法的时候,不需要加(),因此上面的request.user实际上是在调用该user方法,其中user方法实现了如下功能,首先是判断在request中是否有_user属性,如果没有则调用_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()

它这里做的第一步便是循环self.authenticators,前面提到self.authenticators是[对象,对象,对象],即我们自定义的认证组件,然后就是调用自定义认证组件的authenticate方法,这里回顾一下我们写的自定义组件,其源码如下:

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed


class MyAuthentication(BaseAuthentication):
    def authenticate(self, request):
        # 用户认证步骤:
        # 1.读取请求传递的token
        # 2.校验合法性
        # 3.三种返回值
        #   3.1 认证成功,返回元组(11,22)     
        #   11,22分别存入request.user和request.auth
        #   3.2 认证失败,抛出异常   返回错误信息
        #   3.3 返回None(不常用),用于多个认证类

        # /xxx/xxx/?token=1231dd
        # token = request._request.GET.get('token')
        # drf的request源码中的query_params=_request.GET
        token = request.query_params.get('token')
        # 这里获取到请求传递的token后,理应校验合法性,不过这里先略去
        if token:
            return "user", token
        # raise AuthenticationFailed("认证失败")
        # 如果返回字符串,字符串会放入detail中,如果返回字典,则该字典会代替detail
        raise AuthenticationFailed({"code": 0, "msg": "认证失败"})

因此便是将"user"和 token作为返回值赋值给了user_auth_tuple,然后便是判定user_auth_tuple 是否为空,不为空即将user_auth_tuple赋值给self.user和self.auth,注意这里self.user和self.auth仍然是方法而不是属性,以self.user为例,其源码为:

@user.setter
    def user(self, value):
        """
        Sets the user on the current request. This is necessary to maintain
        compatibility with django.contrib.auth where the user property is
        set in the login and logout functions.

        Note that we also set the user on Django's underlying `HttpRequest`
        instance, ensuring that it is available to any middleware in the stack.
        """
        self._user = value
        self._request.user = value

与上面的@property不同,这里的@user.setter是赋值方法,里面的self._user = value,self._request.user = value就是将user_auth_tuple中的第一个值"user"分别赋值给了他们,前者self._user有值了之后便可在@property的那段代码那里通过if语句判断,直接返回self._user,而后者的值则更为常用,当我们要调取当前登录的用户时,即可用request._request.user获取,而在上一节request对象的学习中,我们知道在Request类中定义了__getattr__方法,因此我们可以直接使用request.user调取

以上便是drf的认证组件的所有源码分析

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hemameba

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值