Loading

43.Permission源码解析和自定义权限类

drf的权限类位于permission模块
 

如何确定权限

  • 认证、限流,权限决定是否应该接收请求或拒绝访问
  • 权限检查在视图的最开始处执行,在继续执行其他代码前
  • 权限检查通常会使用request.user和request.auth属性中的身份认证信息来决定是否允许请求
  • 不同级别的用户访问不同的api过程中,使用权限来控制访问的许可
  • DRF框架的权限被定义为一个权限类的列表,表示拥有列表中所有类型的权限
  • 在运行视图代码主体之前,会检查列表中的每个权限
  • 任何一个权限检查失败的话,会抛出 exceptions.PermissionDenied 或 exceptions.NotAuthenticated 异常,视图主体代码不会运行
  • 权限检查失败时,会返回403 Forbidden或401 Unauthorized响应
  • 响应规则如下:
    1. 请求已成功通过身份验证,但不具备访问权限,返回403 Forbidden响应。
    2. 请求未通过身份认证,并且最高优先级的认证类未使用 WWW-Authenticate 标头, 返回403 Forbidden响应。
    3. 请求未通过身份认证,但是最高优先级的认证类使用了 WWW-Authenticate 标头,返回HTTP 401未经授权的响应,并附带适当的WWW-Authenticate报头
 

如何设置权限

#settings全局权限配置

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES':(
        #drf默认的权限,允许任何访问
        'rest_framework.permissions.AllowAny',
    )
}
 
# 局部视图级别的权限配置
class UserInfo(ModelViewSet):
    # 列表传入要设置的权限
    permission_classses = []
'''
全局权限和局部权限都有配置,以局部配置为准,装饰器请求的权限设置同样

'''
 
 

源码解析

BasePermission:权限基类

都直接返回True没有实现具体功能,留了两个坑待后续具体继承实现

class BasePermission(metaclass=BasePermissionMetaclass):


    def has_permission(self, request, view):
       
        return True

    def has_object_permission(self, request, view, obj):
        return True

AllowAny权限

继承权限基类,没有做任何操作直接返回True,任何请求都可以访问
class AllowAny(BasePermission):

    def has_permission(self, request, view):
        return True

IsAuthenticated权限

允许任何经过身份验证的用户访问API,拒绝任何未经身份验证的用户
class IsAuthenticated(BasePermission):

    def has_permission(self, request, view):
        # request.user,如果有值,说明当前有用户,判断用户是否登录
        # request.user.is_authenticated,判断用户是否通过认证
        # 如果两个都通过,bool返回True,可以后续执行,否则False不可执行
        return bool(request.user and request.user.is_authenticated)

IsAdminUser权限

只允许管理员用户访问,拒绝其他任意身份用户访问
class IsAdminUser(BasePermission)

    def has_permission(self, request, view):
        # request.user,如果有值,说明当前有用户,判断用户是否登录
        # request.user.is_staff属性Django 的auth的属性,如果是True,说明是管理员
        # 如果两个都通过,bool返回True,可以后续执行,否则False不可执行
        return bool(request.user and request.user.is_staff)

IsAuthenticatedOrReadOnly权限:

允许经过身份验证的用户完全访问(可读可写),未经过身份验证的用户只允许读取
class IsAuthenticatedOrReadOnly(BasePermission):


    def has_permission(self, request, view):
        return bool(
            # SAFE_METHODS是定义的安全方法list,GET、HEAD、OPTIONS三种请求方式
            # 如果请求方式是三种安全方式之一,直接可以访问,三种读取请求
            # 或者 request.user and request.user.is_authenticated,判断是否登录以及是否验证通过
            # 两种条件满足一种,如果为True,只读或者读写,否则拒绝访问
            request.method in SAFE_METHODS or
            request.user and
            request.user.is_authenticated
        )

DjangoModelPermissions权限

Django模型级别的权限,与Django标准的 django.contrib.auth model权限相关,此权限只能应用于具有 .queryset 属性集的视图。只有在用户通过身份验证并分配了相关模型权限的情况下,才可以授予此权限
class DjangoModelPermissions(BasePermission):
    #权限类属性设定
    perms_map = {
        'GET': [], # 空list,不需要具有模型权限
        'OPTIONS': [],# 空list,不需要具有模型权限
        'HEAD': [], # 空list,不需要具有模型权限
        'POST': ['%(app_label)s.add_%(model_name)s'], # POST 请求需要用户对 add 模型具有权限。
        'PUT': ['%(app_label)s.change_%(model_name)s'], #PUT请求要求用户对 change 模型具有权限。
        'PATCH': ['%(app_label)s.change_%(model_name)s'],#POST请求要求用户对 change 模型具有权限。
        'DELETE': ['%(app_label)s.delete_%(model_name)s'], #DELETE 请求需要用户对 delete 模型具有权限
    }

    authenticated_users_only = True   
  
    def get_required_permissions(self, method, model_cls):

        kwargs = { # 获取请求app_label和model_name
            'app_label': model_cls._meta.app_label,
            'model_name': model_cls._meta.model_name
        }
        # 请求方式不在设定的类属性中则抛出异常
        if method not in self.perms_map:
            raise exceptions.MethodNotAllowed(method)
        # self.perms_map[method]获取指定请求方式的值,拼接kwargs
        return [perm % kwargs for perm in self.perms_map[method]]
        
        
        
        
    def _queryset(self, view):
        #断言 view是否有get_queryset方法或者获取queryset指定的值
        assert hasattr(view, 'get_queryset') \
            or getattr(view, 'queryset', None) is not None, (
            'Cannot apply {} on a view that does not set '
            '`.queryset` or have a `.get_queryset()` method.'
        ).format(self.__class__.__name__)
        # 如果view有getqueryset_set
        if hasattr(view, 'get_queryset'):
            queryset = view.get_queryset()
            # queryset赋值view的get_queryset()方法
            # 如果不为空返回 queryset
            assert queryset is not None, (
                '{}.get_queryset() returned None'.format(view.__class__.__name__)
            )
            return queryset
        return view.queryset

    def has_permission(self, request, view):
    # 如果 ignore_model_permissions有值,返回True
    if getattr(view, '_ignore_model_permissions', False):
        return True
    # 如果没有登录或者 没有认证属性返回False
    if not request.user or (
       not request.user.is_authenticated and self.authenticated_users_only):
        return False
    
    queryset = self._queryset(view)
    # 通过self.get_required_permissions设定对应权限
    perms = self.get_required_permissions(request.method, queryset.model)
    return request.user.has_perms(perms)        
        
      
    '''   
    也可以通过自定义模型权限,重写以上的默认行为
    自定义模型权限,请覆盖 DjangoModelPermissions 并设置 .perms_map 属性
    在重写了 get_queryset() 方法的视图中使用此权限,有可能这个视图上却没有queryset 属性。在这种情况下,建议使用保护性的查询集来标记视图,以便确定所需的权限
    queryset = User.objects.none()
    '''

DjangoModelPermissionsOrAnonReadOnly权限

继承DjangoModelPermissions,与之类似 ,但允许未经身份验证的用户对API的只读权限
class DjangoModelPermissionsOrAnonReadOnly(DjangoModelPermissions):

    authenticated_users_only = False
 

DjangoObjectPermissions权限

继承DjangoModelPermissions,Django的模型的对象级别的权限,粒度最细
  • 与 DjangoModelPermissions 一样,此权限只能应用于具有 .queryset 属性或 .get_queryset() 方法的视图,只有在用户通过身份验证并且具有相关的每个对象权限 和相关的模型权限 后,才会被授予此权限
  • POST 请求要求用户对模型实例具有 add 权限。
  • PUT 和 PATCH 请求要求用户对模型示例具有 change 权限。
  • DELETE 请求要求用户对模型示例具有 delete 权限
  • 和 DjangoModelPermissions 一样,可以通过重写 DjangoObjectPermissions 并设置 .perms_map 属性来使用自定义模型权限
class DjangoObjectPermissions(DjangoModelPermissions):
 
    perms_map = {
        'GET': [],
        'OPTIONS': [],
        'HEAD': [],
        'POST': ['%(app_label)s.add_%(model_name)s'],
        'PUT': ['%(app_label)s.change_%(model_name)s'],
        'PATCH': ['%(app_label)s.change_%(model_name)s'],
        'DELETE': ['%(app_label)s.delete_%(model_name)s'],
    }

    def get_required_object_permissions(self, method, model_cls):
        kwargs = { # 获取app_label和model_name
            'app_label': model_cls._meta.app_label,
            'model_name': model_cls._meta.model_name
        }
        # 如果请求方式不在类属性定义中,抛出异常

        if method not in self.perms_map:
            raise exceptions.MethodNotAllowed(method)
        # 返回对应权限属性
        return [perm % kwargs for perm in self.perms_map[method]]

    def has_object_permission(self, request, view, obj):
       # 获取queryset、model以及当前user
        queryset = self._queryset(view) 
        model_cls = queryset.model
        user = request.user
        # 获取返回的权限属性
        perms = self.get_required_object_permissions(request.method, model_cls)
        # 如果返回的权限属性有任何一个为False
        if not user.has_perms(perms, obj):
            # 如果不是安全请求中的method,抛出404异常
            if request.method in SAFE_METHODS:
              
                raise Http404
            # 获取只读的请求,如果没有只读请求抛出404异常
            read_perms = self.get_required_object_permissions('GET', model_cls)        
            if not user.has_perms(read_perms, obj):
                raise Http404

  
            return False

        return True
 

自定义权限

自定义权限规范

  1. 继承BasePermission权限基类,根据实现需要编写对应的基类方法
  2. has_permission(self,request,view)
    1. view针对哪个视图进行权限检查
    2. 返回值True,通过权限检查
    3. 返回值False,没有通过权限检查
  3. has_object_permission(self,request,view,obj)
    1. 针对单独的对象进行权限检查
    2. 如果视图不是继承了通用视图,需要显示的调用check_object_permissions(request,obj)
    3. 如果继承了通用视图但是重写了get_object(),也需要显示的调用check_object_permissions(request,obj)
    4. 因为在通用视图中自动调用了check_object_permissions,而非通用视图没有自动调用
  1. 可以根据是否安全请求返回不同的权限,可以直接判断请求是否在permissions.SAFE_METHODS
    if request.method in permissions.SAFE_METHODS:
      '''安全请求'''
    else:
      '''写入操作'''
  1. message,权限提示的异常信息,可根据需要决定是否要自定义
# 简单示例

from rest_framework import permissions


# 视图级别权限
class BlackListPermission(permissions.BasePermission):
    # 自定义权限提示信息
    message = '木子七是黑名单'

    def has_permission(self, request, view):
        # 如果登录名是木子七 返回False,不是木子七 返回True
        return not request.user.username == '木子七'


# 对象级别权限

class IsOwnerOrReadOnly(permissions.BasePermission):

    def has_object_permission(self, request, view, obj):
        # 如果是安全请求直接返回True
        if request.method in permissions.SAFE_METHODS:
            return True

        # 判断操作对象的用户名 是否和登录的用户名一致
        return obj.user.username == request.user.username

 

posted @ 2022-10-13 14:39  木子七  阅读(138)  评论(0编辑  收藏  举报