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

Django+GraphQL字段权限控制:异常时避免返回null字段

Django + GraphQL 权限控制与字段返回问题

我用Django + GraphQL构建后端,在models.py中定义了User模型的部分字段:

email = models.EmailField(_("email address"), max_length=320, unique=True)
telephone = models.CharField(max_length=20, null=True, default=None, blank=True)

对应的UserType定义如下:

import graphene
from graphene_django import DjangoObjectType

from main.graphql import validators
from main.models import User


class UserType(DjangoObjectType):
    class Meta:
        model = User
        fields = (
            "id",
            "first_name",
            "last_name",
            "email",
            "birthdate",
            "birthplace",
        )
    def resolve_telephone(self, info):
        validators.verify_request_from_staff_user_or_from_superuser_if_target_staff(
            info, self, info.context.user, "telephone"
        )
        return self.telephone
    def resolve_verified_email(self, info):
        validators.verify_request_from_staff_user_or_from_superuser_if_target_staff(
            info, self, info.context.user, "verified_email"
        )
        return self.verified_email

其中verify_request_from_staff_user_or_from_superuser_if_target_staff函数会在用户无权限访问字段时抛出graphql.GraphQLError异常。

我定义了testUser查询,根据ID返回用户。请求email字段时,返回的data中不包含该字段(符合预期),但请求telephone字段时,data中会出现telephone: null的内容。

示例查询及返回

查询email:

query testEmail {
  testUser(id: 9) {
    email
  }
}

返回结果:

{
  "errors": [
    {
      "message": "Cannot access field 'email'",
      "locations": [
        {
          "line": 3,
          "column": 5
        }
      ],
      "path": [
        "testUser",
        "email"
      ]
    }
  ],
  "data": {
    "testUser": null
  }
}

查询telephone:

query testTelephone {
  testUser(id: 9) {
    telephone
  }
}

返回结果:

{
  "errors": [
    {
      "message": "Cannot access field 'telephone'",
      "locations": [
        {
          "line": 8,
          "column": 5
        }
      ],
      "path": [
        "testUser",
        "telephone"
      ]
    }
  ],
  "data": {
    "testUser": {
      "telephone": null
    }
  }
}

我知道这是因为telephone字段在模型中设置了null=True,但不想修改数据库字段。请问如何实现无权限时响应中不包含该字段?另外,当前为每个字段编写解析器做权限控制的方式是否正确?有没有更高效的实现方法?


一、实现无权限时telephone字段不返回的方法

问题根源在于:email字段在模型中是非nullable的,因此GraphQL类型也是非nullable,当解析抛出异常时,父对象testUser会被置为null;而telephone是nullable字段,抛出异常时会返回null并保留父对象。

要实现无权限时字段不出现在data中,同时保留错误信息,可以使用graphene.UNSET常量:

修改resolve_telephone方法,捕获权限验证异常,添加错误到请求上下文,然后返回UNSET:

import graphene
from graphql import GraphQLError

def resolve_telephone(self, info):
    try:
        validators.verify_request_from_staff_user_or_from_superuser_if_target_staff(
            info, self, info.context.user, "telephone"
        )
        return self.telephone
    except GraphQLError as e:
        # 将错误添加到请求的错误列表
        info.errors.append(e)
        # 返回UNSET,该字段不会被包含在data中
        return graphene.UNSET

同样的逻辑可以应用到resolve_verified_email方法上。这样处理后,无权限时data中不会出现该字段,同时errors数组会保留对应的错误提示。


二、更高效的权限控制实现方式

当前为每个字段编写单独解析器的方式是可行的,但重复代码较多,推荐以下几种优化方案:

1. 使用装饰器封装权限逻辑

把权限验证逻辑封装成装饰器,减少重复代码:

from functools import wraps
import graphene
from graphql import GraphQLError

def staff_or_superuser_required(field_name):
    def decorator(resolver):
        @wraps(resolver)
        def wrapper(self, info, **kwargs):
            try:
                validators.verify_request_from_staff_user_or_from_superuser_if_target_staff(
                    info, self, info.context.user, field_name
                )
                return resolver(self, info, **kwargs)
            except GraphQLError as e:
                info.errors.append(e)
                return graphene.UNSET
        return wrapper
    return decorator

# 在解析器上应用装饰器
class UserType(DjangoObjectType):
    # ... 省略Meta部分
    @staff_or_superuser_required("telephone")
    def resolve_telephone(self, info):
        return self.telephone
    
    @staff_or_superuser_required("verified_email")
    def resolve_verified_email(self, info):
        return self.verified_email

2. 使用字段级权限中间件

编写全局中间件,统一处理所有字段的权限验证,无需为每个字段编写解析器:

import graphene
from graphql import GraphQLError

class FieldPermissionMiddleware:
    def resolve(self, next_resolver, root, info, **kwargs):
        # 定义需要权限控制的字段列表
        restricted_fields = {"telephone", "verified_email", "email"}
        field_name = info.field_name
        
        if field_name in restricted_fields:
            try:
                validators.verify_request_from_staff_user_or_from_superuser_if_target_staff(
                    info, root, info.context.user, field_name
                )
            except GraphQLError as e:
                info.errors.append(e)
                return graphene.UNSET
        
        # 执行后续解析逻辑
        return next_resolver(root, info, **kwargs)

# 在创建Schema时添加中间件
schema = graphene.Schema(
    query=YourQueryType,
    mutation=YourMutationType,
    middleware=[FieldPermissionMiddleware()]
)

3. 自定义权限控制基类

创建带有权限处理逻辑的DjangoObjectType基类,让需要权限控制的类型继承该基类:

from graphene_django import DjangoObjectType
import graphene
from graphql import GraphQLError

class PermissionControlledDjangoObjectType(DjangoObjectType):
    class Meta:
        abstract = True

    def resolve_restricted_field(self, info, field_name):
        try:
            validators.verify_request_from_staff_user_or_from_superuser_if_target_staff(
                info, self, info.context.user, field_name
            )
            return getattr(self, field_name)
        except GraphQLError as e:
            info.errors.append(e)
            return graphene.UNSET

# 继承基类实现UserType
class UserType(PermissionControlledDjangoObjectType):
    class Meta:
        model = User
        fields = ("id", "first_name", "last_name", "email", "birthdate", "birthplace")
    
    telephone = graphene.String()
    verified_email = graphene.Boolean()

    def resolve_telephone(self, info):
        return self.resolve_restricted_field(info, "telephone")
    
    def resolve_verified_email(self, info):
        return self.resolve_restricted_field(info, "verified_email")

内容的提问来源于stack exchange,提问作者Zeno Dalla Valle

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 08:57:04