ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

drf 认证校验及源码分析

2020-10-31 23:01:58  阅读:179  来源: 互联网

标签:models self request 校验 token 源码 user import drf


认证校验

   认证校验是十分重要的,如用户如果不登陆就不能访问某些接口。

   再比如用户不登陆就不能够对一个接口做哪些操作。

   drf中认证的写法流程如下:

   1.写一个类,继承BaseAuthentication,并且覆写其authenticate方法

   2.当认证通过后应该返回两个值,并且第一个值会传递给request.user这个属性中,第二个值将会传递给request.auth这个属性中

   3.如果认证失败,则抛出异常APIException或者AuthenticationFailed,它会自动捕获并返回

   4.当前认证类设置是全局使用还是局部使用

准备工作

   我们有一个登录功能,并且还有一个查询商品的接口,只有当用户登录后才能进行查询,否则就不可以。

模型表

   两张表如下:

from django.db import models


class User(models.Model):
    # 用户
    user_id = models.AutoField(primary_key=True)
    user_name = models.CharField(max_length=32)
    user_password = models.CharField(max_length=32)
    user_token = models.CharField(max_length=64,unique=True,null=True)  # token,唯一

    def __str__(self):
        return self.user_name

    class Meta:
        db_table = ""
        managed = True
        verbose_name = "User"
        verbose_name_plural = "Users"

class Merchandise(models.Model):
    # 商品
    merchandise_id = models.AutoField(primary_key=True)
    merchandise_name = models.CharField(max_length=32)
    merchandise_price = models.IntegerField()

    def __str__(self):
        return self.merchandise_name

    class Meta:
        db_table = ""
        managed = True
        verbose_name = "Merchandise"
        verbose_name_plural = "Merchandises"

   用户表的数据如下:

   image-20201031170412050

   商品表的数据如下:

   image-20201031171236819

   现在,只有当用户登录后,才能够访问商品的接口。

   也就是说,用户的token自动如果为空,将会被认为没有登陆。

序列类

   下面是序列类,我们只展示商品,用户列表将不会展示:

from rest_framework.serializers import ModelSerializer
from . import models

class MerchandiseModelSerializer(ModelSerializer):
    class Meta:
        model = models.Merchandise
        fields = "__all__"
        

视图

   视图,我们只写了关于用户登录与商品的接口:

from uuid import uuid4
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet

from . import models
from . import ser
from . import authLogin  # 导入认证的文件

class MerchandiseAPI(ModelViewSet):
    queryset = models.Merchandise.objects.all()
    serializer_class = ser.MerchandiseModelSerializer

class Login(APIView):
    def post(self,request):
        # 代表用户登录
        login_msg = {
            "user_name": request.data.get("user_name"),
            "user_password": request.data.get("user_password"),
        }
        user_obj = models.User.objects.filter(**login_msg).first()
        if user_obj:
            token = uuid4()  # 生成随机字符串
            user_obj.user_token = token
            user_obj.save()
            return Response(data="登录成功",headers={"token":token})  # 返回随机字符串
        else:
            return Response(data="登录失败,用户名或密码错误")

url

   使用自动生成路由:

from django.contrib import admin
from django.urls import path, re_path
from rest_framework.routers import SimpleRouter

from app01 import views

router = SimpleRouter()
router.register("merchandises",views.MerchandiseAPI)

urlpatterns = [
    path('admin/', admin.site.urls),
    path('login/',views.Login.as_view()),
]
urlpatterns.extend(router.urls)


基本使用

认证类

   接下来我们要书写一个认证类:

from rest_framework.authentication import BaseAuthentication  # 继承的基类
from rest_framework.exceptions import AuthenticationFailed  # 异常
from . import models
from django.http import request

class LoginVerify(BaseAuthentication):
    def authenticate(self, request):
        token = request.META.get("HTTP_TOKEN")
        # 如果在请求头中设置的是token的key名,获取时一定要全大写并加上HTTP
        if not token:
            raise AuthenticationFailed("请求失败,请求头中缺少token")
        else:
            user_obj = models.User.objects.filter(user_token=token).first()  # 获取用户对象
            if user_obj:
                return user_obj,user_obj.user_token  # 返回用户本身和token。这样request.user里面就能拿到该用户了
            else:
                raise AuthenticationFailed("token不存在,用户不存在,请不要伪造登录")

局部使用

   只需要在商品接口中设置一个类属性,该接口便会进行认证。

class MerchandiseAPI(ModelViewSet):
    authentication_classes = [authLogin.LoginVerify]  # 使用认证
    queryset = models.Merchandise.objects.all()
    serializer_class = ser.MerchandiseModelSerializer 

全局使用

   只需要在settings.py中进行配置。

REST_FRAMEWORK={
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.authLogin.LoginVerify",]
}

   如果想取消某个接口的认证,则在其中设置类属性authentication_classes是一个空列表。

   如下所示,登录功能不需要验证,我们对他取消掉即可。

class Login(APIView):
	authentication_classes = []

源码分析

流程分析

   由于modelViewSet继承自APIView,所以我们直接看as_view(),在下面这一句代码中,将会对request进行二次封装。

    def dispatch(self, request, *args, **kwargs):

        self.args = args
        self.kwargs = kwargs
        request = self.initialize_request(request, *args, **kwargs)  # 这里
        self.request = request
        self.headers = self.default_response_headers  # deprecate?

   在二次封装中,实例化出了一个Request对象并返回了,在实例化时,会调用self.get_authenticators()方法,此时的self是我们自定义的视图类,切记这一点。

    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()的代码,可以看见它会循环self.authentication_classes这个可迭代对象,如果你没有传递这个可迭代对象,那么该对象是一个默认的设置。

    def get_authenticators(self):
        return [auth() for auth in self.authentication_classes] # ( authLogin.LoginVerify调用,实例化 )

   如果没有传递,将会找到APIView中的默认设置:

	class APIView(View):
        renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
        parser_classes = api_settings.DEFAULT_PARSER_CLASSES
        authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES  # 默认的设置,默认的认证类,可以自己看一下

   如果有进行传递,可以发现它是使用了一个括号,这就代表会调用,由于传入的是一个类,所以它会进行实例化。

   所以我们可以认为request.authenticators这个参数是一个tuple,里面包含了认证类的实例化对象。

   然后,request就被二次包装完毕了。接下来执行 self.initial(),现在的self依然是我们自定义的视图类。

    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)

   下面是self.inital()的代码,

    def initial(self, request, *args, **kwargs):

        self.format_kwarg = self.get_format_suffix(**kwargs)

        self.perform_authentication(request)  # 只看这个,认证相关的
        self.check_permissions(request)
        self.check_throttles(request)

   到了self.perform_authentication()时,它传递进了个request,并且会去找user这个属性抑或是被property装饰的方法,所以我们需要到Request这个类中去找,需要注意的是如果user是一个方法,这代表会自动传递进self,此时的self则是我们经过二次封装的request对象。

   可以发现它是一个被装饰的方法。很显然我们没有_user这个方法或属性,会执行with语句,其实直接看self._authenticate()即可。再次强调,此次的self是二次封装的request对象。

    @property
    def user(self):
        if not hasattr(self, '_user'):
            with wrap_attributeerrors():
                self._authenticate()
        return self._user

   下面是整个代码的核心。

    def _authenticate(self):
 
        for authenticator in self.authenticators:  # 循环认证类对象 ( authLogin.LoginVerify的实例化 )
            try:
                user_auth_tuple = authenticator.authenticate(self) # 这里会找authenticate方法并将request对象进行传递,我们的认证类继承了BaseAuthentication这个类,它会实现一个接口方法, 但会抛出异常。
            except exceptions.APIException:   # 如果没有实现接口方法,或在验证时抛出异常都会被这里捕获
                self._not_authenticated()  # 执行这里 self.user将会是匿名用户AnonymousUser,而self.auth则是None
                raise

            if user_auth_tuple is not None:  # 如果返回的值不是空
                self._authenticator = authenticator  
                self.user, self.auth = user_auth_tuple  # 分别赋值给self.user,以及self.auth中
                return  # 返回

        self._not_authenticated()  # 上面有认证对象就会return,没有还是设置匿名用户和None

最后总结

   其实看了源码后,你可以发现我们的认证类可以不继承BaseAuthentication,但是推荐继承会更规范,因为这个基类实现了抽象接口。

   其次,它将返回的两个值分别赋值给了request.user以及request.auth

   如果你没有返回值,那么对应的,request.user就是匿名用户,request.auth就是None

   如果你没有配置认证类,其实它会走默认的认证类。

   老规矩,关于配置认证类时依旧是先用局部的,再用全局的,最后是用默认的,如果你的上面的源码确实有感觉了的话,应该能够看懂。

标签:models,self,request,校验,token,源码,user,import,drf
来源: https://www.cnblogs.com/Yunya-Cnblogs/p/13908204.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有