GraphQL新手求助:如何在GraphQL.js/Apollo Server中实现基于角色的授权
基于角色的授权在Apollo Server/GraphQL.js中的实现方案
嘿,作为GraphQL新手就能提前考虑到权限控制,这点真的很赞!我来一步步给你拆解怎么实现贴合你场景的角色授权——毕竟你已经有带roles字段的用户表了,咱们从请求到解析器再到进阶优化,全流程给你捋清楚。
第一步:在请求上下文中获取当前用户的角色
不管是Apollo Server还是纯GraphQL.js,首先得在每次请求时拿到当前操作的用户角色。通常的做法是:
- 客户端请求时携带身份凭证(比如JWT令牌);
- 服务器在**上下文(Context)**里解析凭证,从用户表查询出该用户的
roles字段; - 把用户角色挂载到上下文对象,供后续解析器使用。
举个Apollo Server的例子:
const { ApolloServer } = require('@apollo/server'); const { startStandaloneServer } = require('@apollo/server/standalone'); const jwt = require('jsonwebtoken'); // 模拟从数据库查用户角色的函数 async function getUserRoles(userId) { // 这里替换成你的实际数据库查询逻辑 const user = await db.query('SELECT roles FROM users WHERE id = $1', [userId]); return user?.roles || []; // 假设roles是数组格式,比如['USER', 'ADMIN'] } const server = new ApolloServer({ typeDefs, resolvers }); const { url } = await startStandaloneServer(server, { context: async ({ req }) => { // 从请求头获取JWT const token = req.headers.authorization?.split(' ')[1]; if (!token) { return { userRoles: [] }; // 未登录用户角色为空 } // 解析JWT拿到用户ID const decoded = jwt.verify(token, 'your-secret-key'); // 查询用户角色 const userRoles = await getUserRoles(decoded.userId); return { userRoles }; }, });
第二步:定义权限规则(集中管理更清晰)
建议把每个GraphQL操作(Query/Mutation)需要的角色集中定义,避免散落在各个解析器里,维护起来更方便。比如:
// permissions.js const PERMISSIONS = { Query: { // 查看用户列表需要管理员或版主权限 viewUsers: ['ADMIN', 'MODERATOR'], // 查看自己的资料只要是登录用户即可 viewProfile: ['USER', 'ADMIN', 'MODERATOR'], }, Mutation: { // 创建用户仅管理员有权限 createUser: ['ADMIN'], // 更新自己的资料只要是登录用户 updateProfile: ['USER', 'ADMIN', 'MODERATOR'], }, }; module.exports = PERMISSIONS;
第三步:在解析器中添加权限校验
写一个通用的校验函数,然后在每个需要权限的解析器里调用它。如果用户角色不在允许列表里,直接抛出GraphQLError。
const { GraphQLError } = require('graphql'); const PERMISSIONS = require('./permissions'); // 通用权限校验函数 function checkPermissions(requiredRoles, userRoles) { // 如果不需要权限(比如公开接口),直接通过 if (!requiredRoles || requiredRoles.length === 0) return true; // 未登录用户直接拒绝 if (!userRoles || userRoles.length === 0) { throw new GraphQLError('你需要登录才能执行此操作', { extensions: { code: 'UNAUTHENTICATED' }, }); } // 检查用户角色是否包含至少一个所需角色 const hasPermission = requiredRoles.some(role => userRoles.includes(role)); if (!hasPermission) { throw new GraphQLError('你没有权限执行此操作', { extensions: { code: 'FORBIDDEN' }, }); } } // 示例解析器 const resolvers = { Query: { viewUsers: async (_, __, { userRoles }) => { // 校验权限 checkPermissions(PERMISSIONS.Query.viewUsers, userRoles); // 后续数据库查询逻辑 return await db.query('SELECT * FROM users'); }, viewProfile: async (_, { userId }, { userRoles, decodedUserId }) => { checkPermissions(PERMISSIONS.Query.viewProfile, userRoles); // 额外:普通用户只能查看自己的资料,管理员可以看所有 if (!userRoles.includes('ADMIN') && userId !== decodedUserId) { throw new GraphQLError('你只能查看自己的资料', { extensions: { code: 'FORBIDDEN' }, }); } return await db.query('SELECT * FROM users WHERE id = $1', [userId]); }, }, Mutation: { createUser: async (_, { input }, { userRoles }) => { checkPermissions(PERMISSIONS.Mutation.createUser, userRoles); // 创建用户的逻辑 return await db.query('INSERT INTO users (...) VALUES (...) RETURNING *', [...]); }, }, };
进阶:用自定义指令(Directive)简化校验
如果觉得每个解析器都写checkPermissions太繁琐,可以用Apollo Server的自定义指令,把权限校验逻辑封装成指令,直接在Schema里标注,更优雅。
1. 定义指令的Schema
directive @requiresRole(roles: [String!]!) on FIELD_DEFINITION type Query { viewUsers: [User!]! @requiresRole(roles: ["ADMIN", "MODERATOR"]) viewProfile(userId: ID!): User! @requiresRole(roles: ["USER", "ADMIN", "MODERATOR"]) } type Mutation { createUser(input: CreateUserInput!): User! @requiresRole(roles: ["ADMIN"]) }
2. 实现指令的逻辑
const { ApolloServer, SchemaDirectiveVisitor } = require('@apollo/server'); const { defaultFieldResolver } = require('graphql'); const { GraphQLError } = require('graphql'); class RequiresRoleDirective extends SchemaDirectiveVisitor { visitFieldDefinition(field) { const { resolve = defaultFieldResolver } = field; const { roles } = this.args; field.resolve = async function (...args) { const [_, __, context] = args; const { userRoles } = context; // 权限校验逻辑 if (!userRoles || userRoles.length === 0) { throw new GraphQLError('你需要登录才能执行此操作', { extensions: { code: 'UNAUTHENTICATED' }, }); } const hasPermission = roles.some(role => userRoles.includes(role)); if (!hasPermission) { throw new GraphQLError('你没有权限执行此操作', { extensions: { code: 'FORBIDDEN' }, }); } // 执行原解析器逻辑 return resolve.apply(this, args); }; } } // 创建Apollo Server时注册指令 const server = new ApolloServer({ typeDefs, resolvers, schemaDirectives: { requiresRole: RequiresRoleDirective, }, });
额外提醒:数据库层面的防护
即使解析器的权限校验没问题,也建议在数据库查询时做额外过滤——比如普通用户查询用户列表时,只能看到自己的信息;管理员能看到所有。这样可以避免因为解析器逻辑漏洞导致的越权访问,形成双重保障。
比如普通用户查询自己的资料时,数据库查询条件加上WHERE id = $1(当前用户ID),而管理员则去掉这个条件。
内容的提问来源于stack exchange,提问作者Yue Teng
相关产品推荐
相关产品推荐

