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

Django阻止用户跨归属创建Product实例的权限问题排查

Django DRF 跨关联模型创建时的权限校验问题修复

问题场景

定义了4层外键关联的模型:

class User(models.Model):
    name = models.CharField()

class Business(models.Model):
    name = models.CharField() # 商家名称
    created_by = models.ForeignKey(User,related_name="businesses",on_delete=models.CASCADE)

class ProdCategory(models.Model):
    business = models.ForeignKey(Business,related_name="categories",on_delete=models.CASCADE)
    name = models.CharField()

class Product(models.Model):
    category = models.ForeignKey(ProdCategory,related_name="products",on_delete=models.CASCADE)
    name = models.CharField() # 原代码此处写为ForeignKey为笔误
    price = models.DecimalField(max_digits=10, decimal_places=2)

列表查询逻辑正常,可正确返回当前登录用户沿User -> Business -> ProdCategory -> Product链路归属的产品,但创建产品时存在权限漏洞:用户传入其他用户名下的ProdCategory id时,依然可以成功创建产品。
原有视图代码尝试通过重写create方法实现校验,但存在两个问题:

  • 传入其他用户的ProdCategory id时校验不生效,依然能创建成功
  • 传入当前用户自己的ProdCategory id时抛出错误,提示category字段需为ProdCategory实例,不能传入数值
    原有错误视图代码:
class CategoryItemListCreateView(generics.ListCreateAPIView):
    serializer_class = CategoryItemSerializer

    def get_queryset(self):
        return CategoryItem.objects.filter(category__business__created_by=self.request.user).order_by('order')
    
    def create(self, request, *args, **kwargs):
        response = super().create(request, *args, **kwargs)
        item_data = response.data   
        # 单个商家对应多个分类
        categories = ProdCategory.objects.filter(business__created_by=request.user)
        for c in categories:
            if item_data['category'] == c.id:
                Product.objects.create(name=item_data['name'], category=item_data['category'])
        return response

根因分析

  • 校验时机错误:校验逻辑写在super().create()调用之后,此时父类方法已经完成了数据校验和数据库写入,后续的校验逻辑根本无法拦截非法请求,自然会出现越权创建的问题。
  • 外键传参格式错误:调用Product.objects.create()时直接传入了分类id的数值,Django ORM的外键字段默认要求传入模型实例,若需要直接传id值,需要使用外键名_id的参数格式(比如category_id=item_data['category'])。
  • 逻辑冗余低效:遍历当前用户所有分类去比对id的写法完全没有必要,既增加数据库查询压力,逻辑也容易出错。

修复方案

最规范的实现是把权限校验下沉到序列化器层,在数据入库前完成校验,完全移除视图层冗余的手动创建逻辑:

1. 在序列化器中添加分类归属校验

重写序列化器的validate_category方法,在反序列化阶段就校验传入的分类是否属于当前用户:

from rest_framework import serializers
from .models import Product, ProdCategory

class ProductSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = "__all__"

    def validate_category(self, category_instance):
        # 传入的category_instance是根据前端传的id查询到的ProdCategory实例
        if category_instance.business.created_by != self.context["request"].user:
            raise serializers.ValidationError("仅可使用自己名下的分类创建产品")
        return category_instance

DRF通用视图会自动把request对象注入到序列化器的context属性中,无需手动传递。

2. 简化视图层代码

校验逻辑已经在序列化器中实现,不需要重写create方法,直接使用通用视图的默认逻辑即可:

from rest_framework import generics
from .serializers import ProductSerializer
from .models import Product

class ProductListCreateView(generics.ListCreateAPIView):
    serializer_class = ProductSerializer

    def get_queryset(self):
        return Product.objects.filter(
            category__business__created_by=self.request.user
        ).order_by("id")

修复后效果:

  • 前端传入其他用户名下的分类id时,序列化器校验阶段就会返回400错误,不会写入数据库
  • 正常传入自己名下的分类id时,可正常创建产品,不会出现外键类型错误
  • 代码逻辑更简洁,没有冗余的数据库查询

内容的提问来源于stack exchange,提问作者Imtiaz Ahmed

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 03:39:44