Django REST Framework 自定义 Permission 类实现数据级鉴权怎么做?

文章导读
在 Django REST Framework 中实现数据级鉴权,核心是继承 BasePermission 并重写 has_object_permission 方法。对于列表接口,必须配合视图的 get_queryset 方法进行数据过滤,否则会导致非所有者可见数据列表(数据泄露)。
📋 目录
  1. 快速实现方案
  2. 全局配置(可选)
  3. 验证是否生效
  4. 常见坑与排查
  5. 参考来源
A A

在 Django REST Framework 中实现数据级鉴权,核心是继承 BasePermission 并重写 has_object_permission 方法。对于列表接口,必须配合视图的 get_queryset 方法进行数据过滤,否则会导致非所有者可见数据列表(数据泄露)。

先说结论:自定义权限类需继承 BasePermission,针对对象级操作必须实现 has_object_permission 方法,列表接口需重写 get_queryset

  • 先判断:确认是列表操作还是对象详情操作,后者才触发 has_object_permission
  • 优先做:在权限类中校验 request.method 是否为安全方法,使用 DRF 内置 SAFE_METHODS 常量。
  • 再验证:使用不同用户 Token 测试接口,确认非所有者收到 403 响应,且列表接口不显示他人数据。

快速实现方案

数据级鉴权不需要复杂配置,核心是编写一个权限类并挂载到视图。以下是一个典型的“仅所有者可修改”权限类实现,同时包含列表数据过滤:

from rest_framework.permissions import BasePermission, SAFE_METHODS

class IsOwnerOrReadOnly(BasePermission):
    def has_object_permission(self, request, view, obj):
        # 使用内置常量替代硬编码列表
        if request.method in SAFE_METHODS:
            return True
        # 确保 obj 有 owner 字段,且当前用户已认证
        return hasattr(obj, 'owner') and obj.owner == request.user

在视图中通过 permission_classes 属性应用该类,并重写 get_queryset 防止列表数据泄露:

from rest_framework import viewsets
from .models import Article
from .permissions import IsOwnerOrReadOnly

class ArticleViewSet(viewsets.ModelViewSet):
    permission_classes = [IsOwnerOrReadOnly]
    serializer_class = ArticleSerializer

    def get_queryset(self):
        # 列表接口必须过滤,否则权限类不会生效
        user = self.request.user
        if user.is_authenticated:
            return Article.objects.filter(owner=user)
        return Article.objects.none()

全局配置(可选)

如果希望项目默认启用认证和权限检查,可在 settings.py 中配置。注意权限类执行依赖于认证类,需确保 DEFAULT_AUTHENTICATION_CLASSES 已配置:

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication',
    ]
}

验证是否生效

1. 状态码检查

Django REST Framework 自定义 Permission 类实现数据级鉴权怎么做?

使用非所有者的用户 Token 访问受保护的对象接口(如 PUT/DELETE),预期应返回 403 Forbidden 状态码。如果是未登录用户访问需要认证的接口,应返回 401 Unauthorized。

2. 列表数据隔离测试

创建两个用户 A 和 B。用 A 创建数据,尝试用 B 的 Token 访问列表接口 GET /articles/。确保返回结果中不包含 A 创建的数据。如果未重写 get_queryset,B 可能会看到 A 的数据列表,尽管无法修改。

3. 日志观察

在权限类方法中临时添加打印语句,观察服务器控制台日志,确认 has_object_permission 是否被触发。如果列表接口未触发该方法是正常现象,因为列表接口不走对象级权限检查。

常见坑与排查

1. 列表接口权限失效

Django REST Framework 自定义 Permission 类实现数据级鉴权怎么做?

DRF 的 has_object_permission 仅在 detail 路由(如 /articles/1/)生效。在 list 路由(如 /articles/)上,因为没有具体对象实例,该方法不会被调用。必须在视图的 get_queryset 中根据 self.request.user 进行过滤。

2. 用户未认证导致报错

权限检查依赖于 request.user。如果认证类(Authentication)配置不当,request.user 可能是 AnonymousUser 对象。若代码直接访问 request.user.id 可能报错,建议先判断 request.user.is_authenticated

3. 安全方法误拦

如果在 has_object_permission 中没有放行安全方法,可能导致前端无法正常读取数据。务必检查 request.method 是否在 SAFE_METHODS 中,不要硬编码方法名。

参考来源