You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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中实现。

  1. 修正后的权限类代码:
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
  1. 修正后的视图代码:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 15:18:13