Django Rest Framework自定义权限校验不生效问题排查
问题根因
你的权限校验完全不生效、出现越权访问,核心是踩了DRF权限机制的几个典型误区:
- 对象级权限触发条件不满足:DRF自定义权限的
has_object_permission方法,仅在视图调用get_object()获取单个模型实例时才会自动执行,通常用于详情、更新、删除这类操作单条资源的接口。你的GET接口是列表查询,直接执行Account.objects.all()拉取全量数据返回,全程没有触发单实例校验流程,这就是你把权限逻辑写死返回False,接口依然正常返回数据的核心原因。 has_permission逻辑空转:你后续补充的has_permission方法仅做了登录校验,和配置里的permissions.IsAuthenticated功能完全重复,没有做任何数据范围拦截,自然不会限制返回结果。- 字段值不匹配:更新后的权限代码里,判断的
account_type值为employee/provider/jool-admin,和模型中定义的staff/manager/admin枚举值完全不对应,就算触发校验也无法命中正确分支。 - 外键比较逻辑错误:原代码中
obj.staff.manager == request.user.id拿模型实例和整数主键值做比较,永远不会返回True,会导致直属manager也无法查看下属员工记录。 - 额外低级错误:视图POST方法中
serializer.is_valid(raise_exception=ValueError)写法错误,raise_exception参数需要传布尔值,传异常类会导致校验逻辑异常。
修复方案
核心原则:列表接口的数据范围控制不能依赖has_object_permission,必须在查询数据集阶段就按用户权限做过滤,仅单资源操作的校验放在has_object_permission中实现。
- 修正后的权限类代码:
from rest_framework import permissions from accounts.models import Staff class CanViewUserRecord(permissions.BasePermission): edit_methods = ("PUT", "PATCH") def has_permission(self, request, view): # 基础校验:仅登录用户可访问接口 return bool(request.user and request.user.is_authenticated) def has_object_permission(self, request, view, obj): # 仅用于单条用户记录(详情/更新/删除)的权限校验 request_user = request.user # 管理员直接放行所有操作 if request_user.account_type == "admin": return True # 本人永远可以访问、操作自己的记录 if obj == request_user: return True if request.method in permissions.SAFE_METHODS: # 普通员工记录:直属管理员可访问 if obj.account_type == "staff": # 用manager_id直接比较主键值,避免额外实例查询,也不会出现类型不匹配 return hasattr(obj, 'staff') and obj.staff.manager_id == request_user.id # 管理员、超管账号记录,除本人和超管外其他人无权访问 if obj.account_type in ["manager", "admin"]: return False return False if request.method in self.edit_methods: # 管理员仅能编辑自己名下的普通员工记录 if obj.account_type == "staff" and request_user.account_type == "manager": return hasattr(obj, 'staff') and obj.staff.manager_id == request_user.id return False return False
- 修正后的视图代码:
from django.db.models import Q from rest_framework import views, status, response from rest_framework import permissions from .models import Account from .serializers import AccountSerializer class UserRecordView(views.APIView): """ 支持接口: GET:返回当前用户有权限查看的用户列表 POST:创建新用户 """ permission_classes = [permissions.IsAuthenticated, CanViewUserRecord] def get(self, request, format=None): request_user = request.user # 按用户角色过滤可见的用户范围,禁止直接返回全量数据 if request_user.account_type == "admin": visible_users = Account.objects.all() elif request_user.account_type == "manager": # 管理员可见范围:本人 + 自己名下所有普通员工,兜底禁止看到超管账号 staff_user_ids = Staff.objects.filter(manager=request_user).values_list('user_id', flat=True) visible_users = Account.objects.filter( Q(id=request_user.id) | Q(id__in=staff_user_ids) ).exclude(account_type="admin") else: # 普通员工仅能看到自己的记录 visible_users = Account.objects.filter(id=request_user.id) serializer = AccountSerializer(visible_users, many=True) return response.Response(serializer.data) def post(self, request): request_user = request.user # 补充创建操作的权限校验:普通员工无权创建用户 if request_user.account_type not in ["admin", "manager"]: return response.Response( {"error": True, "error_msg": "无创建用户权限"}, status=status.HTTP_403_FORBIDDEN ) serializer = AccountSerializer(data=request.data) # 修正is_valid的raise_exception参数 serializer.is_valid(raise_exception=True) serializer.save() return response.Response(serializer.data, status=status.HTTP_201_CREATED)
额外注意事项
- 如果后续新增单用户详情、更新、删除接口,调用
get_object()获取用户实例时,DRF会自动执行has_object_permission的校验逻辑,不需要额外写判断。 - 所有列表类接口的权限控制,必须从查询集层面做过滤,不要尝试遍历全量数据逐个调用对象级权限,会有严重的性能问题,也不符合DRF的设计逻辑。
内容的提问来源于stack exchange,提问作者TheSunbathingCrow
相关产品推荐
相关产品推荐

