• rest framework之权限组件


    一、权限组件的使用

    1、自定义权限

    要实现自定义权限,需要重写BasePermission并实现以下方法中的一个或两个

    • .has_permission(self, request, view)
    • .has_object_permission(self, request, view, obj)

    如果请求被授予访问权限,方法应该返回True,否则返回False

    @six.add_metaclass(BasePermissionMetaclass)
    class BasePermission(object):
        """
        A base class from which all permission classes should inherit.
        """
    
        def has_permission(self, request, view):
            """
            Return `True` if permission is granted, `False` otherwise.
            """
            return True
    
        def has_object_permission(self, request, view, obj):
            """
            Return `True` if permission is granted, `False` otherwise.
            """
            return True
    BasePermission

      权限组件需要与认证组件进行配合,因为一个用户只有认证登录,拿到用户信息。此时根据认证后的用户拿到相应的权限进行操作,下面是基于rbac权限控制如何利用

    restframework中的权限组件。

    from django.conf import settings
    import re
    from rest_framework.permissions import BasePermission
    
    
    class Permission(BasePermission):
    
        message = "无权限访问"
    
        def has_permission(self, request, view):
            """
            Return `True` if permission is granted, `False` otherwise.
            """
            permissions_dict = {}
            menus_dict = {}
    
            """
            从数据库拿到该用户的所有权限,
            request.user是认证后拿到的元组第一个值
            """
            permissions_queryset = request.user.roles.filter(
                permissions__url__isnull=False).values(
                'permissions__id',
                'permissions__url',
                'permissions__title',
                'permissions__parent_id',
                'permissions__action__code',
                'permissions__menu_id',
                'permissions__menu__title',
                'permissions__menu__icon',
                'permissions__menu__position').distinct()
    
            """
            将用户的权限进行数据结构的调整
            {
    
                '/index.html': ['GET','POST','DEL','EDIT],
                r'/detail-(d+).html': ['GET','POST','DEL','EDIT],
    
            }
            """
            for row in permissions_queryset:
                if row["permissions__url"] in permissions_dict:
                    permissions_dict[row["permissions__url"]].append(
                        row["permissions__action__code"])
                else:
                    permissions_dict[row["permissions__url"]] = [
                        row["permissions__action__code"], ]
            """
            初始化用户菜单,也就是该用户可以访问的菜单
            """
            for row in permissions_queryset:
                menu_id = row["permissions__menu_id"]
                if not menu_id:
                    continue
    
                if menu_id not in menus_dict:
                    menus_dict[row["permissions__menu__position"]] = {
                        "id": row["permissions__menu_id"],
                        "title": row["permissions__menu__title"],
                        "icon": row["permissions__menu__icon"],
                        "children": [
                            {
                                'id': row['permissions__id'],
                                'title': row['permissions__title'],
                                'url': row['permissions__url']
    
                            }
                        ]
                    }
    
                else:
                    menus_dict[row["permissions__menu__position"]]["children"].append(
                        {
                            'id': row['permissions__id'],
                            'title': row['permissions__title'],
                            'url': row['permissions__url']
    
                        }
                    )
            """
            将该用户的权限赋值给视图,这样在视图中可以拿到对应的权限,
            传给前端进行按钮级别的权限验证
            """
            view.permissions_dict = permissions_dict
            """
            将该用户的菜单赋值给视图,这样在视图中可以拿到对应的菜单,
            传给前端进行左侧菜单的初始化
            """
            view.menus_dict = menus_dict
    
            """
            过滤白名单
            """
    
            for pattern in settings.RBAC_NO_AUTH_URL:
    
                if re.match(pattern, request.path_info):
                    return None
    
            if not permissions_dict:
                return False
    
            # 请求url与用户拥有的权限进行匹配
            """
            /crm/menus {'/rights': ['get'], '/user': ['get', 'post'], '/roles': ['get']}
            """
            flag = False
            for pattern, code_list in permissions_dict.items():
                upper_code_list = [item.upper() for item in code_list]
                request_permission_code = request.method
    
                if re.match(pattern, request.path_info):
                    if request_permission_code in upper_code_list:
                        permission_code_list = upper_code_list
    
                        # 将用户角色拥有的请求方式赋值给视图
                        view.PERMISSION_CODE_LIST_KEY = permission_code_list
    
                        flag = True
                        break
    
            if not flag:
                return False
    
            return True

    当认证类完成后,就可以进行全局注册或者局部注册:

    • 全局注册
    REST_FRAMEWORK = {
    
        # 全局使用的认证类
        "DEFAULT_AUTHENTICATION_CLASSES":['crm.utils.auth.AuthToken',],
        #全局权限类
        "DEFAULT_PERMISSION_CLASSES": ['rbac.permissions.Permission', ],
    
    
    }

      值得注意的是,在全局权限中使用了认证过的用户,所以必须要有认证类,这样才会针对某个用户进行权限的控制,认证相关请参考:https://www.cnblogs.com/shenjianping/p/11387324.html

      其次,登录视图中不需要认证和权限,所以应该免认证和权限验证,只需要在相应的视图中进行配置:

    class LoginView(APIView):
    
        authentication_classes = []  # 登陆页面免认证
        permission_classes = [] #登录免权限
        
        def post(self,request,*args,**kwargs)
            pass
        
        ...
    •  局部注册

    只需要在每一个视图中进行配置即可:

    from rbac.permissions import Permission
    
    class UserModelView(GenericViewSet):
    
        permission_classes = [Permission,] #局部权限配置
        
        def list(self,request,*args,**kwargs)
              pass
    
        ...

    2、内置权限

    • AllowAny

    允许不受限制的访问,而不管该请求是否已通过身份验证或未经身份验证

    class AllowAny(BasePermission):
        """
        Allow any access.
        This isn't strictly required, since you could use an empty
        permission_classes list, but it's useful because it makes the intention
        more explicit.
        """
    
        def has_permission(self, request, view):
            return True
    AllowAny
    • IsAuthenticated

    拒绝任何未经身份验证的用户的权限,并允许其他权限

    class IsAuthenticated(BasePermission):
        """
        Allows access only to authenticated users.
        """
    
        def has_permission(self, request, view):
            return bool(request.user and request.user.is_authenticated)
    IsAuthenticated
    • IsAuthenticatedOrReadOnly

    允许经过身份验证的用户执行任何请求。只有当请求方法是“安全”方法(GETHEAD 或 OPTIONS)之一时,才允许未经授权的用户请求

    class IsAuthenticatedOrReadOnly(BasePermission):
        """
        The request is authenticated as a user, or is a read-only request.
        """
    
        def has_permission(self, request, view):
            return bool(
                request.method in SAFE_METHODS or
                request.user and
                request.user.is_authenticated
            )
    IsAuthenticatedOrReadOnly
    • IsAdminUser

    除非user.is_staffTrue,否则IsAdminUser权限类将拒绝任何用户的权限,在这种情况下将允许权限

    class IsAdminUser(BasePermission):
        """
        Allows access only to admin users.
        """
    
        def has_permission(self, request, view):
            return bool(request.user and request.user.is_staff)
    IsAdminUser

    二、源码剖析

    在restframework中对View类封装成了APIView,而APIView中主要是dispatch方法的不同,在其方法中进行了request的重构以及加入一些组件(认证组件、权限组件等)。

        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
            #rest-framework重构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
                #这里和CBV一样进行方法的分发
                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

    执行self.initial(request, *args, **kwargs)中:

     def initial(self, request, *args, **kwargs):
            """
           
            ...
    
            # Ensure that the incoming request is permitted
            self.perform_authentication(request) #进行认证
            self.check_permissions(request) #权限相关
            self.check_throttles(request)

    执行self.check_permissions(request)中:

        def check_permissions(self, request):
            """
            Check if the request should be permitted.
            Raises an appropriate exception if the request is not permitted.
            """
            for permission in self.get_permissions():
                #如果没有权限抛出异常
                if not permission.has_permission(request, self):
                    self.permission_denied(
                        request, message=getattr(permission, 'message', None)
                    )

    在这里进行权限的检验,循环所有的权限类对象(self.get_permissions())。

        def get_permissions(self):
            """
            Instantiates and returns the list of permissions that this view requires.
            """
            return [permission() for permission in self.permission_classes]

    所以,可以在视图中局部配置permission_classes这样的权限类的列表,另外循环的每一个权限类中必须实现has_permission方法,如果有权限就返回True,如果没有就返回false,

    如果返回False就会执行check_permissions中的permission_denied方法,会抛出一个异常。

        def permission_denied(self, request, message=None):
            """
            If request is not permitted, determine what kind of exception to raise.
            """
            if request.authenticators and not request.successful_authenticator:
                raise exceptions.NotAuthenticated()
            raise exceptions.PermissionDenied(detail=message)

    当然自己也是可以更改与异常关联的错误消息,直接在自定义权限类实现消息属性。

    from rest_framework.permissions import BasePermission
    
    
    class Permission(BasePermission):
    
        message = "无权限访问"
        
        ...

     参考文档:https://www.django-rest-framework.org/api-guide/permissions/

  • 相关阅读:
    常用开发技巧系列(三)
    快速排序OC、Swift版源码
    一步一步学习SignalR进行实时通信_8_案例2
    一步一步学习SignalR进行实时通信_7_非代理
    一步一步学习SignalR进行实时通信_6_案例
    一步一步学习SignalR进行实时通信_5_Hub
    一步一步学习SignalR进行实时通信_4_Hub
    一步一步学习SignalR进行实时通信_3_通过CORS解决跨域
    一步一步学习SignalR进行实时通信_2_Persistent Connections
    一步一步学习SignalR进行实时通信_1_简单介绍
  • 原文地址:https://www.cnblogs.com/shenjianping/p/11489166.html
Copyright © 2020-2023  润新知