• 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

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

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

  • 相关阅读:
    简直不敢相信...
    halcon 保存Region [原创]
    VS2015 下载链接
    C#【数据转换】十进制yte[]相互转换
    C# 复制窗体问题完美解决办法
    TextBox 保持固定长度,添加新行滚动到最后,跨线程。
    mysql isnull
    C#跨线程访问控件[我的记录]
    C# 常用类-IO-ClassExcelExport
    C# 常用类-IO-ClassXML
  • 原文地址:https://www.cnblogs.com/Yunya-Cnblogs/p/13908204.html
Copyright © 2020-2023  润新知