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

DRF中如何规范实现普通用户仅操作自身数据、管理员全量操作的权限控制(替代list方法硬编码校验)

DRF中如何规范实现普通用户仅操作自身数据、管理员全量操作的权限控制(替代list方法硬编码校验)

这个问题其实是DRF里很常见的权限控制场景——普通用户只能操作自己的数据,管理员拥有全量权限,你遇到的列表接口(api/users/)权限漏洞,核心原因是DRF的对象级权限不会作用在list动作上:因为列表返回的是多个实例,DRF不会逐个触发has_object_permission校验,而模型级权限只要用户有view_user权限就会放行,导致普通用户能看到所有用户的列表数据。

下面给你几个更规范的解决方案,替代你在list方法里硬编码的方式:

方案一:在权限类中针对list动作单独校验(推荐)

把权限逻辑全部集中在自定义权限类里,通过has_permission方法对list动作做专属校验,这样不用修改视图的业务逻辑,完全符合DRF的权限设计思路:

from rest_framework import permissions

class UserAccessPermission(permissions.BasePermission):
    def has_permission(self, request, view):
        # 对list动作单独拦截:只有admin角色能访问用户列表
        if view.action == "list":
            return get_user_role(request.user) == "admin"
        # 其他动作(retrieve、update、partial_update等),放行到模型权限和对象权限校验
        return True

    def has_object_permission(self, request, view, obj):
        # 单个实例的权限校验:自己或admin都能操作
        return (request.user == obj) or (get_user_role(request.user) == "admin")

然后你的UserViewset不需要修改list方法,保持原来的配置即可:

class UserViewset(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = [MyAppModelPermissions & UserAccessPermission]

这个方案的优势是权限逻辑完全内聚在权限类中,视图只需要负责业务逻辑,代码结构更清晰,也更容易维护。

方案二:动态返回不同动作的权限类

DRF的ViewSet支持通过get_permissions方法,针对不同动作返回不同的权限组合。这种方式更灵活,适合需要对多个动作做差异化权限控制的场景:

class UserViewset(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = [MyAppModelPermissions & UserAccessPermission]

    def get_permissions(self):
        # 对list动作,仅允许admin访问(这里可以用你自己的admin角色校验逻辑)
        if self.action == "list":
            return [
                MyAppModelPermissions(), 
                permissions.BasePermission() 
                if get_user_role(self.request.user) == "admin" 
                else permissions.DjangoModelPermissionsOrAnonReadOnly()
            ]
        # 其他动作沿用默认权限组合
        return super().get_permissions()

如果你的admin用户是Django默认的is_superuser或is_staff类型,也可以直接用DRF自带的IsAdminUser权限类简化校验。

方案三:过滤查询集(返回用户自身数据而非拦截)

如果你的产品需求允许普通用户访问列表接口,但只能看到自己的数据(而不是直接返回403),可以通过重写get_queryset方法来过滤数据,这也是DRF推荐的规范做法:

class UserViewset(viewsets.ModelViewSet):
    serializer_class = UserSerializer
    permission_classes = [MyAppModelPermissions & UserAccessPermission]

    def get_queryset(self):
        current_user = self.request.user
        # admin返回所有用户,普通用户只返回自己
        if get_user_role(current_user) == "admin":
            return User.objects.all()
        return User.objects.filter(id=current_user.id)

这种方式的优点是用户体验更友好,普通用户访问api/users/会返回仅包含自己的列表数据,而不是权限错误。

为什么不推荐你原来的硬编码方式?

你之前在list方法里直接返回403的方式虽然能解决问题,但缺点是权限逻辑和视图业务逻辑耦合,后续如果需要调整权限规则,你得去视图里找代码,而不是统一在权限类中维护,违背了单一职责原则。上面的几种方案都更贴合DRF的设计理念,代码也更优雅易维护。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 09:33:04