• Django rest framework 源码分析 (1)----认证


    一、基础

    django 2.0官方文档

    https://docs.djangoproject.com/en/2.0/

    安装

    pip3 install djangorestframework

    假如我们想实现用户必须是登陆后才能访问的需求,利用restframework该如何的去实现,具体的源码流程又是怎么样的呢

    为了有一个清晰的认识,先直接上代码,有一个清晰的认识,在剖析源码流程

    首先先创建一个应用  

    python manage.py startapp app01

    在应用 app01.views.py 下 的视图函数的代码如下

    from rest_framework.views import APIViewfrom rest_framework import exceptions
    from rest_framework.request import Request
    
    class MyAuthentication(object):
        def authenticate(self,request):
            token = request._request.GET.get('token')
            # 获取用户名和密码,去数据校验
            if not token:
                raise exceptions.AuthenticationFailed('用户认证失败')
            return ("dog",None)
    
        def authenticate_header(self,val):
            pass
    
    class DogView(APIView):
        authentication_classes = [MyAuthentication,]
    
        def get(self,request,*args,**kwargs):
            print(request)
            print(request.user)
            self.dispatch
            ret  = {
                'code':1000,
                'msg':'xxx'
            }
            return HttpResponse(json.dumps(ret),status=201)

    在项目的根目录下的urls.py 中添加路由的代码如下:

    from app01 import views
    
    
    urlpatterns = [
        url(r'^dog/', views.DogView.as_view()),
    ]

    如果想实现必须是登陆后才能进行请求的话,只需重写父类中的  authenticate  (进行一些逻辑认证)和 authenticate_header   就能达到认证的效果

    启动项目,在浏览器中输入

    http://127.0.0.1:8000/dog/?token=123

    ‘携带token返回的结果如下

    {"code": 1000, "msg": "xxx"}
    

      

    没有携带token返回的结果如下

    http://127.0.0.1:8000/dog
    

      

    源码分析

    那么上面的源码内部又是如何是现代的呢

    源码流程图

     源码入口先从  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进行封装
            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进行了封装   

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

    那么它内部做了些什么呢 ,追踪  self.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
            )

     我们可以看到    authenticators=self.get_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_classes]

    通过上面的代码我们可以看到 其返回的是通过列表生成式返回的一个实例化对象    那么它是通过什么生成这个对象的呢,下面我们来继续追踪  self.authentication_classes 其代码如下所示:

    class APIView(View):
    
        # The following policies may be set at either globally, or per-view.
        renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
        parser_classes = api_settings.DEFAULT_PARSER_CLASSES
        authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES

     到这里我们发现它是通过读取应用的配置文件,到这里神秘的面纱已经开始解开了,加入我们自己写的类如果自己的内部有  authentication_classes  这个属性,并且其也是一个可迭代的对象,那么根据面向对象的属性,就会执行我们

    自己的self.authentication_classes  ,而不是默认到配置文件中去找。

    对request的总结:

    上面对原生的request对象进行了一些封装和获得了认证的实例话对象的列表

    执行认证

    继续追踪 dispatch  中的  self.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 的意思是执行认证的意思,让我们追踪其内部的代码如下:

        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 ,追踪其 内部的user发现其是一个实例属性,代码如下所示

    点进去可以看到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

     让我们进行追踪  self._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()

    执行认证类的authenticate方法

       这里分三种情况

         1.如果authenticate方法抛出异常,self._not_authenticated()执行后在抛出异常-----就是认证没通过

         2.有返回值,必须是元组:(request.user,request.auth)

         3.返回None,表示当前认证不处理,等下一个认证来处理

    返回值就是例子中的:

    token_obj.user-->>request.user
    token_obj-->>request.auth
    

    通过认证自定义的authenticated没有返回值,就执行self._not_authenticated(),相当于匿名用户,

    def _not_authenticated(self):
            """
            Set authenticator, user & authtoken representing an unauthenticated request.
    
            Defaults are None, AnonymousUser & None.
            """
            self._authenticator = 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

     因为authenticate方法我们自己重写了,所以当执行authenticate()的时候就是执行我们自己写的认证

    父类中的authenticate方法

        def authenticate(self, request):
            return (self.force_user, self.force_token)

    我们自己写的

        def authenticate(self,request):
            token = request._request.GET.get('token')
            # 获取用户名和密码,去数据校验
            if not token:
                raise exceptions.AuthenticationFailed('用户认证失败')
            return ("dog",None)

    所以到这里我们就搞懂了我们在做用户认证的时候为什么需要重其authenticate的方法了

     

    以上的是我们自己定义的 authentication_classes   它会去我们的类属性中使用我们自己的,只需在需要认证接口的中设置类属性 authentication_classes = [ 认证的逻辑函数 ]

    全局认证

    假如我们进行全局认证的话,可以在配置文件中设置,通过继承父类的方式来实现,简单的说就是 类属性 authentication_classes 自己不定义使用父类的,看以下的代码:

    class APIView(View):
    
        # The following policies may be set at either globally, or per-view.
        renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
        parser_classes = api_settings.DEFAULT_PARSER_CLASSES
        authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES

    在以上的代码中我们可以看到authentication_classes = = api_settings.DEFAULT_AUTHENTICATION_CLASSES 我们可以追踪看到代码如下:

    def reload_api_settings(*args, **kwargs):
        setting = kwargs['setting']
        if setting == 'REST_FRAMEWORK':
            api_settings.reload()
    
    
    setting_changed.connect(reload_api_settings)

    这是它会去配置文件中读取    REST_FRAMEWORK ,这时后我们可以在配置文件中添加这个配置,里面的键是 DEFAULT_PARSER_CLASSES 值是认证函数的路径

    REST_FRAMEWORK = {
        # 全局使用的认证类
        "DEFAULT_AUTHENTICATION_CLASSES":['api.utils.auth.Authtication', ],
    }

     我们根据上述配置的路径,在应用api建立以下文件  utils/auth.py  代码如下:

    class Authtication(BaseAuthentication):
        def authenticate(self,request):
            token = request._request.GET.get('token')
            token_obj = models.UserToken.objects.filter(token=token).first()
            if not token_obj:
                raise exceptions.AuthenticationFailed('用户认证失败')
            # 在rest framework内部会将整个两个字段赋值给request,以供后续操作使用
            return (token_obj.user, token_obj)
    
        def authenticate_header(self, request):
            pass

    这时后所有的接口,必须经过认证后才能访问,但是有的接口是不需要认证的,比如登陆的时候这时,我们可以在登陆的接口中不走父类(apiview) 的认证使用我们自己 的,只需定义定义类属性authentication_classes = [ ] 即可

    class AuthView(APIView):
        """
        用于用户登录认证
        """
        authentication_classes = []
    
        def post(self,request,*args,**kwargs):
            pass

    关于匿名用户登陆的设置

    有些时候用户是在没有登陆的时候,访问我们的接口,这时候我们称之为匿名的用户,我们可以对其进行简单的设置

        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()
    
        def _not_authenticated(self):
            """
            Set authenticator, user & authtoken representing an unauthenticated request.
    
            Defaults are None, AnonymousUser & None.
            """
            self._authenticator = None
    
            if api_settings.UNAUTHENTICATED_USER:
                self.user = api_settings.UNAUTHENTICATED_USER()
            else:
                self.user = None
    
            if api_settings.UNAUTHENTICATED_TOKEN:
                self.auth = api_settings.UNAUTHENTICATED_TOKEN()
            else:
                self.auth = None

    在上面的源码中我们可以看到 匿名用户默认的request.user=AnonymousUser, request.auth = None ,我们来写一个接口来简单的认证以下

    class UserInfoView(APIView):
    
        authentication_classes = []
    
        def get(self,request,*args,**kwargs):
            print(request.user)
            print(request.auth )
            return HttpResponse('用户信息')

    为其  配置url 

     url(r'^api/v1/info/$', views.UserInfoView.as_view()),

    在浏览器中输入

    http://127.0.0.1:8000/api/v1/info/ 

    测试的结果如下:

     

     源码中的user 和token 的设置如下:

            if api_settings.UNAUTHENTICATED_USER:
                self.user = api_settings.UNAUTHENTICATED_USER()
            else:
                self.user = None
    
            if api_settings.UNAUTHENTICATED_TOKEN:
                self.auth = api_settings.UNAUTHENTICATED_TOKEN()

     我们可以看到 对user 和 token 的设置,我们可以定义一个函数,所以我们可以这样设置,

    REST_FRAMEWORK = {
        # 全局使用的认证类
        "DEFAULT_AUTHENTICATION_CLASSES":['api.utils.auth.FirstAuthtication','api.utils.auth.Authtication', ],
        # "UNAUTHENTICATED_USER":lambda :"匿名用户",
        "UNAUTHENTICATED_USER": None
        "UNAUTHENTICATED_TOKEN":None,
    }

     测试的结果如下:

     

    drf的内置认证类

    rest_framework里面内置了一些认证,我们自己写的认证类最好都要继承内置认证类 "BaseAuthentication"

    from rest_framework.authentication import BasicAuthentication

    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).
            """
            #内置的认证类,authenticate方法,如果不自己写,默认则抛出异常
            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.
            """
            #authenticate_header方法,作用是当认证失败的时候,返回的响应头
            pass

    其它内置认证类

    rest_framework里面还内置了其它认证类,我们主要用到的就是BaseAuthentication,剩下的很少用到

    总结

     自己写认证类方法梳理

     (1)创建认证类

    • 继承BaseAuthentication    --->>1.重写authenticate方法;2.authenticate_header方法直接写pass就可以(这个方法必须写)

    (2)authenticate()返回值(三种)

    • None ----->>>当前认证不管,等下一个认证来执行
    • raise exceptions.AuthenticationFailed('用户认证失败')       # from rest_framework import exceptions
    •  有返回值元祖形式:(元素1,元素2)      #元素1复制给request.user;  元素2复制给request.auth

     (3)局部使用

    • authentication_classes = [BaseAuthentication,]

    (4)全局使用

    #设置全局认证
    REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES":['API.utils.auth.Authentication',]
    }
    

      

  • 相关阅读:
    php类和对象: 类常量
    类和对象:静态属性、静态方法
    类和对象: 构造方法
    类和对象:成员属性、成员方法 — 学习笔记3
    类和对象:创建对象
    类和对象:类与对象定义
    可扩展定制可复用的倒计时插件
    SeaJS入门
    JS可复用的多条件筛选插件
    谁说转载的文章用户就不喜欢了?
  • 原文地址:https://www.cnblogs.com/crazymagic/p/9511912.html
Copyright © 2020-2023  润新知