如何基于对象自身限制GraphQL Mutation的更新权限?
问题与解决方案
问题背景
数据库对象
[ { "id": 1, "name": "foo", "objType": "A" }, { "id": 2, "name": "bar", "objType": "B" } ]
用户数据
[ { "id": 3, "name": "User A", "role": "admin" }, { "id": 4, "name": "User B", "role": "client" } ]
GraphQL Schema
enum ObjTypeEnum { A B } type MyObj { id: Int name: String objType: ObjTypeEnum } type Mutation { updateObj(id: Int!, name: String): MyObj }
权限需求
- 管理员(User A)可更新任意
MyObj对象 - 普通用户(User B)仅能更新
objType为B的对象
通用实现方式
1. 自定义GraphQL指令
通过自定义指令封装权限校验逻辑,让规则与业务解析器解耦,更贴合GraphQL特性。
步骤1:定义指令
在Schema中添加指令定义:
directive @requireObjType(allowedTypes: [ObjTypeEnum!]) on FIELD_DEFINITION
步骤2:实现指令校验逻辑
以Apollo Server为例,编写指令的解析逻辑:
const requireObjTypeDirective = { async fieldResolver(next, source, args, context, info) { const { user } = context; // 管理员直接放行 if (user.role === 'admin') return next(); // 查询目标对象 const targetObj = await db.MyObj.findById(args.id); // 提取指令配置的允许类型 const allowedTypes = info.fieldNodes[0].directives .find(d => d.name.value === 'requireObjType') .arguments[0].value.values .map(v => v.value); // 校验对象类型 if (!allowedTypes.includes(targetObj.objType)) { throw new Error('无权限更新该类型对象'); } return next(); } };
步骤3:在Schema中应用指令
修改Mutation字段,绑定权限指令:
type Mutation { updateObj(id: Int!, name: String): MyObj @requireObjType(allowedTypes: [B]) }
后续所有非管理员调用updateObj时,会自动触发类型校验,无需在解析器中重复编写逻辑。
2. 通用权限中间件
通过高阶函数包装解析器,将权限逻辑抽离为可复用的中间件,适用于支持中间件机制的GraphQL服务。
示例实现:
// 通用权限校验函数 const checkObjTypePermission = async (args, user, allowedTypes) => { if (user.role === 'admin') return true; const targetObj = await db.MyObj.findById(args.id); return allowedTypes.includes(targetObj.objType); }; // 包装解析器的高阶函数 const withObjTypePermission = (allowedTypes, resolver) => { return async (parent, args, context, info) => { const hasPermission = await checkObjTypePermission(args, context.user, allowedTypes); if (!hasPermission) { throw new Error('无权限更新该类型对象'); } return resolver(parent, args, context, info); }; }; // 应用到updateObj解析器 const resolvers = { Mutation: { updateObj: withObjTypePermission(['B'], async (parent, args) => { // 仅编写业务更新逻辑 return await db.MyObj.update({ id: args.id }, { name: args.name }); }) } };
方案优势
相比直接在解析器中写校验逻辑,以上两种方式的核心优势:
- 权限规则与业务逻辑解耦,解析器只需关注核心业务
- 规则可复用,其他需要相同校验的字段可直接套用
- 指令方式可读性更强,在Schema中就能直观看到字段的权限要求
内容的提问来源于stack exchange,提问作者Sinayra
相关产品推荐
相关产品推荐

