rest framework之权限组件

时间:2019-09-09
本文章向大家介绍rest framework之权限组件,主要包括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/

原文地址:https://www.cnblogs.com/shenjianping/p/11489166.html