在 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. 状态码检查
使用非所有者的用户 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. 列表接口权限失效
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 中,不要硬编码方法名。