Django 与 DRF 的权限控制逻辑

≯℡__Kan透↙ 提交于 2020-08-04 13:46:33

Django 项目中负责权限控制的模块是 contrib.auth。有时为了扩展 行级权限 功能,还会引入一个名为 Guardian 的包。本文的描述都基于 Django + DRF + Guardian 的组合。

model

Django 并不严格遵守 RBAC 的模式,他的“权限”既可以分配给人,也可以分配给组。Guardian 也一样,model 定义如下:

class Permission(models.Model):
    name = models.CharField(_('name'), max_length=255)
    content_type = models.ForeignKey(ContentType, models.CASCADE)
    codename = models.CharField(_('codename'), max_length=100)


class Group(models.Model):
    name = models.CharField(_('name'), max_length=80, unique=True)
    permissions = models.ManyToManyField(Permission)


class UserPermission(models.Model):
    user = models.ForeignKey(User)
    permission = models.ForeignKey(Permission)


class UserObjectPermission():
    user = models.ForeignKey(user_model_label, on_delete=models.CASCADE)
    permission = models.ForeignKey(Permission, on_delete=models.CASCADE)
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_pk = models.CharField(_('object ID'), max_length=255)
    content_object = GenericForeignKey(fk_field='object_pk')
    
class GroupObjectPermission()
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    permission = models.ForeignKey(Permission, on_delete=models.CASCADE)
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_pk = models.CharField(_('object ID'), max_length=255)
    content_object = GenericForeignKey(fk_field='object_pk')

其中 Permission 表的 codename 字段是一种动宾结构,类似 view_user 这样,代表有 User 表的读权限。而 UserObjectPermission 表是在一个 Permission 表的外键基础上,又增加了 object_pk 的信息。因此这个表可以变得非常大,使用时需注意。

Backend

Backend 的功能定位,说的直白一些就是:

查表判断某人是否拥有某种权限。

每种 Backend 负责某种特定的表,或者查表规则。

class ObjectPermissionBackend(object):
    supports_object_permissions = True
    supports_anonymous_user = True
    supports_inactive_user = True

    def authenticate(self, username, password):
        return None

    def has_perm(self, user_obj, perm, obj=None):
        pass

    def get_all_permissions(self, user_obj, obj=None):
        pass


class ModelBackend:
    def get_all_permissions(self, user_obj, obj=None):
        if not user_obj.is_active or user_obj.is_anonymous or obj is not None:
            return set()
        if not hasattr(user_obj, '_perm_cache'):
            user_obj._perm_cache = set()
            user_obj._perm_cache.update(self.get_user_permissions(user_obj))
            user_obj._perm_cache.update(self.get_group_permissions(user_obj))
        return user_obj._perm_cache

    def has_perm(self, user_obj, perm, obj=None):
        if not user_obj.is_active:
            return False
        return perm in self.get_all_permissions(user_obj, obj)

PermissionClass

Permission Class 是 DRF 提供的一种高级权限定义方法。基本定义是:

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

DRF 的 View 会在恰当的时候,比如调用 get_object 之前对用户的权限进行校验。

配置方法

PermissionClass 配置在 DRF 的 View 上,permission_classes = []。如果没有配置,则默认使用 settings.REST_FRAMEWORK.DEFAULT_AUTHENTICATION_CLASSES

当用户请求某个 view 的时候,DRF 这样进行校验:按顺序调用 PermissionClass,只有全部返回 True 才算授权成功。

Backend 配置在 settings.AUTHENTICATION_BACKENDS 这个 list 里面,代表启用这些 Backends。这个列表里的 Backends 可以被 django.contrib.auth.auth.get_backends() 函数获取到。最常用的内建 Backends 调用方有 user.has_perm() 方法,他会迭代每个 backends,任一 backend 返回 True 则校验通过,但如果任意 Backend raise PermissionDenied,则 has_perm 直接返回 False。即对于单一 Backend 来说:

  • 返回 True 一定通过
  • 返回 False 不一定拒绝
  • raise PermissionDenied 一定拒绝

综上,配置好 DRF 权限控制的核心就是处理好 Backend 和 PermissionClass 的关系。他俩分属后端和前端。大体可以按以下规则划分:

  • 需要查数据库的属于 Backend 的范畴
  • 多个权限的逻辑连接,比如 IsStaffOrReadonly 这种,属于 PermissionClass 的范畴
  • 能够影响 user.has_perm() 返回结果的是 Backend,被 user.has_perm() 影响的是 PermissionClass
  • Backend 是全局、客观、通用的,PermissionClass 是部分应用、配置生效的。

举例:

Case1 逻辑权限

指由某种“规则”定义的权限,而不是存储在数据库里,如实现这样一种需求:

如果用户拥有 Model1 的 A 权限,则其自动拥有 Model2 的 B 权限。否则没有权限。

我们分析这个需求需要直接影响 user.has_perm(Model2, B) 的返回结果,因此应该以 Backend 来实现。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!