当前位置 博文首页 > Silent丿丶黑羽:Django(62)自定义认证类

    Silent丿丶黑羽:Django(62)自定义认证类

    作者:Silent丿丶黑羽 时间:2021-06-13 18:24

    前言

    如果我们不用使用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"
    }
    

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

    bk
    下一篇:没有了