从0学DRF(源码剖析)(二)认证

前言

对于DRF框架,学会使用很简单,如果能从源码的角度理解它的一些常用功能的实现,那么对于我们去学另外一门新的框架的时候也可以得心应手。

认证基本使用

视图层

class OrderView(APIView):
    authentication_classes = [Myauthenticate,]
    def get(self, request, *args, **kwargs):
        return JsonResponse('ok')

新建utils文件夹创建专门做认证的py文件
必须实现authenticateauthenticate_header方法(这个是认证失败的时候,给浏览器返回的响应的响应头)

from rest_framework import exceptions
from rest_framework.authentication import BaseAuthentication
from api import models
class Myauthenticate(BaseAuthentication):
    def authenticate(self, request):
        token = request._request.GET.get('token')
        token_obj = models.UserToken.objects.filter(token=token).first()
        """
            在Django的源码里边,这些返回都是很严格的
            如果认证失败,必须抛异常,不能使用return返回东西
            如果认证成功,必须返回一个元组
        """
        if not token_obj:
           raise exceptions.AuthenticationFailed('认证失败')
        # 认证成功必须返回一个元组,
        # 元组的第一个元素DRF会放到request.user里边,
        # 第二个元素给request.auth
        return (token_obj.user, token_obj)
    def authenticate_header(self, request):
        pass

源码分析

源码大致流程分析

记住请求到view里边,如果是CBV的话,会先到类的dispatch里边,然后dispatch根据请求类型找到GET(),POST()等方法然后执行。
先来看看Django的APIview的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进行了封装,
    # 待会我们看看initialize_request做了啥
    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
1)对原生的request进行封装

initialize_request的源码
下图可看到initialize_request加了四个东西,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
    )

get_authenticators(身份验证器源码)

    def get_authenticators(self):
        """
        Instantiates and returns the list of authenticators that this view can use.
        """
        """
        由源码可见这是一个列表推导式,此方法返回了一个auth对象的列表
        """
        return [auth() for auth in self.authentication_classes]

接下来我们看看authentication_classes里面是啥
在这里插入图片描述
可见这是默认的,如果我们自己定义了,那么就会用我们的,因为它是从我们写的类里面找,找不到再去父类APIview中找

然后我们点api_settings进去看看是啥玩意

api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)

接下来点击它继承的defaults类看看是什么
这里的源码写了一大堆,写了很多默认的类,我们主要看它那两个默认认证的类

'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication'
    ],

走到这里的时候,pycharm没法点往下走了,我们导入这两个类看看

from rest_framework.authentication import SessionAuthentication
from rest_framework.authentication import BaseAuthentication

查看BaseAuthentication源码

class BaseAuthentication(object):
    """
    All authentication classes should extend BaseAuthentication.
    """

    def authenticate(self, request):
        """
        Authenticate the request and return a two-tuple of (user, token).
        """
        raise NotImplementedError(".authenticate() must be overridden.")

    def authenticate_header(self, request):
        """
        Return a string to be used as the value of the `WWW-Authenticate`
        header in a `401 Unauthenticated` response, or `None` if the
        authentication scheme should return `403 Permission Denied` responses.
        """
        pass

从源码中也可以看到authenticate方法必须被重写,否则会抛异常
class BaseAuthentication下面还有个BasicAuthentication,这是做具体认证的流程,从headers里面能获取用户名和密码

class BasicAuthentication(BaseAuthentication):
    """
    HTTP Basic authentication against username/password.
    """
    www_authenticate_realm = 'api'

    def authenticate(self, request):
        """
        Returns a `User` if a correct username and password have been supplied
        using HTTP Basic authentication.  Otherwise returns `None`.
        """
        auth = get_authorization_header(request).split()

        if not auth or auth[0].lower() != b'basic':
            return None

        if len(auth) == 1:
            msg = _('Invalid basic header. No credentials provided.')
            raise exceptions.AuthenticationFailed(msg)
        elif len(auth) > 2:
            msg = _('Invalid basic header. Credentials string should not contain spaces.')
            raise exceptions.AuthenticationFailed(msg)

        try:
            auth_parts = base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':')
        except (TypeError, UnicodeDecodeError, binascii.Error):
            msg = _('Invalid basic header. Credentials not correctly base64 encoded.')
            raise exceptions.AuthenticationFailed(msg)

        userid, password = auth_parts[0], auth_parts[2]
        return self.authenticate_credentials(userid, password, request)

    def authenticate_credentials(self, userid, password, request=None):
        """
        Authenticate the userid and password against username and password
        with optional request for context.
        """
        credentials = {
            get_user_model().USERNAME_FIELD: userid,
            'password': password
        }
        user = authenticate(request=request, **credentials)

        if user is None:
            raise exceptions.AuthenticationFailed(_('Invalid username/password.'))

        if not user.is_active:
            raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))

        return (user, None)

    def authenticate_header(self, request):
        return 'Basic realm="%s"' % self.www_authenticate_realm

当然restfulframework默认定义了两个类。我们也可以自定制类,自己有就用自己的了,自己没有就去找父类的了,但是里面必须实现authenticate方法,不然会报错。

2)认证流程

首先看self.initial(request, *args, **kwargs)源码

    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),点进去看看

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

它返回了一个request.user,注意这个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

可以看到user()被设置为静态属性,所以上面没有加括号就直接调用了,点击去看看_authenticate

def _authenticate(self):
"""
Attempt to authenticate the request using each authentication instance
in turn.
"""
# 循环认证类的所有对象
	for authenticator in self.authenticators:
	    try:
	    """
	    执行认证类的authenticate方法,
	    1.如果authenticate方法抛出异常,执行_not_authenticated()
	    2.有返回值,必须是元组:(request.user,request.auth)
	     把元组的第一个元素赋值给self.user(用户),
	     第二个元素赋值给self.auth(用户的token)
	 3.返回None,就不管,下一个认证来处理(raise是向上抛出异常)
	    """
	        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()

然后我们在来看看_not_authenticated()是干啥的,,也就是说如果认证后返回None,会怎么样

    def _not_authenticated(self):
        """
        Set authenticator, user & authtoken representing an unauthenticated request.

        Defaults are None, AnonymousUser & None.
        """
        self._authenticator = None

        # Authentication
        # 'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser',
        # 'UNAUTHENTICATED_TOKEN': None,
        if api_settings.UNAUTHENTICATED_USER:
            self.user = api_settings.UNAUTHENTICATED_USER()   # AnonymousUser(匿名用户
        else:
            self.user = None

        if api_settings.UNAUTHENTICATED_TOKEN:
            self.auth = api_settings.UNAUTHENTICATED_TOKEN()  # None,
        else:
            self.auth = None

可见,如果上面都没处理,我就加个默认值AnonymousUser和None,把request.user赋值给AnonymousUser,request.auth赋值给None。

认证总的流程

加粗样式

  • 第一步,请求到dispatch之后,原生的request被封装,加上了四个东西,其中一个就是用户认证的。
  • 第二步:执行initial()方法里面的perform_authentication
  • 第三步:执行user,user已经被声明为静态属性,
  • 第四部,执行_authenticate()
  • 第五步:循环所有的authenticators的对象,执行对象的authenticate方法。authenticate方法有三种情况
  • 如果authenticate方法返回None,代表什么也不做,由下一个类来进行处理,如果有返回值,返回值一定是个元组,元组的两个元素分别赋值给request.userrequest.auth,第三种情况是抛异常
  • 如果抛异常,执行_not_authenticated方法
  • 到了_not_authenticated方法说明跳过了所有的认证,默认用户和token使用配置文件进行设置
原创文章 85 获赞 120 访问量 4万+

猜你喜欢

转载自blog.csdn.net/happygjcd/article/details/104576558