zoukankan      html  css  js  c++  java
  • Django(62)自定义认证类

    前言

    如果我们不用使用drf那套认证规则,我们想自定义认证类,那么我们首先要知道,drf本身是如何定义认证规则的,也就是要查看它的源码是如何写的
     

    源码分析

    源码的入口在APIView.py文件下的dispatch方法下的self.initial方法中的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
    

    返回了一个requestuser方法,request代表的是drfRequest,所以我们进入drfRequest类中查找user方法属性,源码如下:

        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
    

    上述代码的意思是:返回与当前请求关联的用户,由提供给请求的身份验证类进行身份验证。如果没有用户,我们需要通过_authenticate方法验证,我们查看下它的源码

    def _authenticate(self):
        """
        尝试依次使用每个身份验证实例对请求进行身份验证。
        """
        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验证器其实是调用父类APIViewauthenticatorsAPIViewauthenticators在源码initialize_request方法下的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]
    

    再点击authentication_classes查看

    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    

    我们就知道了drf默认的认证器在settings文件下的DEFAULT_AUTHENTICATION_CLASSES类下面

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

    我们发现drf默认有2个认证类一个基础的认证,另一个session认证,这两个认证类都继承自BaseAuthentication,我们来看下源码

    class BaseAuthentication:
        """
        所有的认证类都继承自BaseAuthentication.
        """
    
        def authenticate(self, request):
            """
             认证请求返回一个二元组(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
    

    接下来我们看下BasicAuthentication如何写的,后续我们依葫芦画瓢

    class BasicAuthentication(BaseAuthentication):
        """
        针对用户名密码的 HTTP 基本身份验证
        """
        www_authenticate_realm = 'api'
    
        def authenticate(self, request):
            """
            如果使用 HTTP 基本身份验证提供了正确的用户名和密码,则返回“User”。否则返回“None”。
            """
            # 获取请求头中`HTTP_AUTHORIZATION`,并进行分割
            auth = get_authorization_header(request).split()
            
            # 如果没有auth或者auth的第一个索引值的小写不等于basic,则返回None
            if not auth or auth[0].lower() != b'basic':
                return None
            
            # auth列表的长度必须等于2,格式['basic', 'abc.xyz.123']
            # 如果auth的长度等于1,则抛出异常
            if len(auth) == 1:
                msg = _('Invalid basic header. No credentials provided.')
                raise exceptions.AuthenticationFailed(msg)
            # 如果长度大于2,也抛出异常
            elif len(auth) > 2:
                msg = _('Invalid basic header. Credentials string should not contain spaces.')
                raise exceptions.AuthenticationFailed(msg)
    
            try:
                try:
                    # auth[1]解码格式为utf-8
                    auth_decoded = base64.b64decode(auth[1]).decode('utf-8')
                except UnicodeDecodeError:
                    auth_decoded = base64.b64decode(auth[1]).decode('latin-1')
                auth_parts = auth_decoded.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
    

    自定义认证类

    1. 创建继承BaseAuthentication的认证类
    2. 实现authenticate方法
    3. 实现体根据认证规则 确定 游客 正常用户 非法用户
    4. 进行全局或局部配置(一般采用全局配置)

    认证规则

    1. 没有认证信息,返回None(游客)
    2. 有认证信息认证失败,抛异常(非法用户)
    3. 有认证信息认证成功,返回用户和认证信息的元组(合法用户)

    我们创建一个文件夹authentications,写入如下代码

    from rest_framework.authentication import BaseAuthentication
    from rest_framework.exceptions import AuthenticationFailed
    from api.models import User
    
    
    class MyAuthentications(BaseAuthentication):
    
        def authenticate(self, request):
            # 前台在请求头携带认证信息
            # 且默认规范用Authorization字段携带认证信息
            # 后台固定在请求对象的META字段中的HTTP_AUTHORIZATION获取
            auth = request.META.get('HTTP_AUTHORIZATION', None)
    
            # 处理游客
            if auth is None:
                return None
    
            auth_list = auth.split()
            if not len(auth_list) == 2 and auth_list[0].lower() == "auth":
                raise AuthenticationFailed("认证信息有误,非法用户")
            # 合法的用户还需要从auth_list[1]中解析出来
            # 注:假设一种情况,信息为xx.yy.zz,就可以解析出admin用户:实际开发,该逻辑一定是校验用户的正常逻辑
            if auth_list[1] != 'xx.yy.zz':  # 校验失败
                raise AuthenticationFailed("用户校验失败,非法用户")
    
            user = User.objects.filter(username='jkc').first()
            print(user)
    
            if not user:
                raise AuthenticationFailed("用户数据有误,非法用户")
    
            return user, None
    

    然后在settings.py中配置全局的自定义认证类

    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': [
            'api.authentications.MyAuthentications'
        ],
    }
    

    最后写入视图函数

    class TestView(APIView):
        def get(self, request, *args, **kwargs):
            return APIResponse(data_msg="drf get ok")
    

    然后我们访问视图,在headers中不传Authorization 代表游客,游客可以访问成功

    {
        "statusCode": 0,
        "message": "drf get ok"
    }
    

    接着我们在请求头中只传auth

    访问视图会抛出异常信息

    {
        "detail": "认证信息有误,非法用户"
    }
    

    然后我们在请求头中传入错误的认证,auth 111

    访问视图会抛出异常信息

    {
        "detail": "用户校验失败,非法用户"
    }
    

    最后我们在请求头中传入正确的认证,auth xx.yy.zz,这次会得到正确的返回结果

    {
        "statusCode": 0,
        "message": "drf get ok"
    }
    

    以上的测试,就代表我们自定义的认证类起作用了

  • 相关阅读:
    CodeForces 734F Anton and School
    CodeForces 733F Drivers Dissatisfaction
    CodeForces 733C Epidemic in Monstropolis
    ZOJ 3498 Javabeans
    ZOJ 3497 Mistwald
    ZOJ 3495 Lego Bricks
    CodeForces 732F Tourist Reform
    CodeForces 732E Sockets
    CodeForces 731E Funny Game
    CodeForces 731D 80-th Level Archeology
  • 原文地址:https://www.cnblogs.com/jiakecong/p/14880244.html
Copyright © 2011-2022 走看看