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

GraphQL新手求助:如何在GraphQL.js/Apollo Server中实现基于角色的授权

基于角色的授权在Apollo Server/GraphQL.js中的实现方案

嘿,作为GraphQL新手就能提前考虑到权限控制,这点真的很赞!我来一步步给你拆解怎么实现贴合你场景的角色授权——毕竟你已经有带roles字段的用户表了,咱们从请求到解析器再到进阶优化,全流程给你捋清楚。

第一步:在请求上下文中获取当前用户的角色

不管是Apollo Server还是纯GraphQL.js,首先得在每次请求时拿到当前操作的用户角色。通常的做法是:

  1. 客户端请求时携带身份凭证(比如JWT令牌);
  2. 服务器在**上下文(Context)**里解析凭证,从用户表查询出该用户的roles字段;
  3. 把用户角色挂载到上下文对象,供后续解析器使用。

举个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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 07:41:10