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

