DRF check-permissions组件源码剖析

59 阅读2分钟

premission组件源码剖析:

第一步,当接收到请求,drf会通过dispatch()对request进行处理

class APIView(View):
    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)

                # 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

对于权限组件,在dispatch中主要看

self.initial(request, *args, **kwargs)
    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)	#认证组件,循环执行每一个authticatef方法,失败会抛出异常
        self.check_permissions(request)			#权限组件#执行到这里时是通过认证或者所有认证类都返回none,在认证类最后中可加入一个检测是否认证成功的类,到此时可确保request.user&auth
        self.check_throttles(request)

执行权限组件时

self.check_permissions(request)	
    def permission_denied(self, request, message=None, code=None):
        #权限等于False,则抛出异常,返回的message和code默认为none,可在认证类中自定义
        if request.authenticators and not request.successful_authenticator:
            raise exceptions.NotAuthenticated()
        raise exceptions.PermissionDenied(detail=message, code=code)

    def get_permissions(self):
        #读取permission_classes中的类并实例化成对象,读取顺序参照上一篇认证组件
        return [permission() for permission in self.permission_classes]

    def check_permissions(self, request):
        for permission in self.get_permissions():
            
            #执行每个权限类的has_permission,如果等于False则进入下方的permission_denied
            if not permission.has_permission(request, self):
                
                #认证不通过抛出会进入permission_denied抛出异常
                self.permission_denied(
                    request,
                    message=getattr(permission, 'message', None),
                    code=getattr(permission, 'code', None)
                )

自定义认证类的返回的错误信息

class Mypermission(BasePermission):
    #自定义错误信息
    message = {"code":10011,"msg":"无权访问"}
    def has_permission(self,request,view):
        a=random.randint(1,3)
        if a ==2:
            return True
        else:
            return False

抛出异常时的返回信息

{"code":"10011","msg":"无权访问"}

permission_denied抛出的异常会被dispatch捕获到,执行except后的处理异常的方法。

现有的权限认证的机制时当所有的认证类都返回True时才能继续执行,能不能改成只要通过一个权限类就可以继续执行下面的代码呢?可以通过修改check_permissions实现。在我们自定义的权限类中可以重写check_permissions:

def check_permissions(self, request):
    permission_err_list=[]
	for permission in self.get_permissions():
        if permission.has_permission(request, self):
            return
        else:
            permission_err_list.append(permission)
    else:
        self.permission_denied( request,message=getattr(permission_err_list[0], 'message', None), code=getattr(permission_err_list[0], 'code', None))