认证授权与错误处理


认证授权与错误处理

认证:让服务器知道你是谁

GraphQL服务通常只有一个HTTP端点 /graphql,这意味着传统的"按URL限制访问"的方式不适用。认证需要在GraphQL层面实现。

最常见的做法是在HTTP请求头中携带令牌,在context创建时解析令牌、验证身份,然后把用户信息放入context,供所有解析器使用。

// 认证中间件 const getUserFromToken = (req: Request): User | null => { const authHeader = req.headers.authorization || ''; const token = authHeader.replace('Bearer ', ''); if (!token) return null; try { return jwt.verify(token, process.env.JWT_SECRET) as User; } catch { return null; } }; const createContext = ({ req }) => ({ user: getUserFromToken(req), loaders: createLoaders(), db: getDbConnection(), }); ​

在解析器中检查认证状态:

const resolvers = { Mutation: { createPost: (_, { input }, context) => { if (!context.user) { throw new AuthenticationError('请先登录'); } // 已认证,继续执行 }, deletePost: (_, { id }, context) => { if (!context.user) { throw new AuthenticationError('请先登录'); } // ... } } }; ​

Apollo Server提供了几个内置的错误类:AuthenticationError、ForbiddenError、UserInputError,它们会自动设置正确的HTTP状态码。

授权:控制谁能访问什么

认证解决"你是谁"的问题,授权解决"你能做什么"的问题。GraphQL的授权可以在三个层次实现:

Schema层:用自定义指令标记需要特定角色的字段。

directive @auth(requires: String = "USER") on FIELD_DEFINITION type Query { publicPosts: [Post!]! myPosts: [Post!]! @auth(requires: "USER") adminDashboard: Dashboard! @auth(requires: "ADMIN") } type Mutation { createPost(input: CreatePostInput!): Post! @auth deleteUser(id: ID!): Boolean! @auth(requires: "ADMIN") } ​

解析器层:在解析器中直接检查权限。这是最灵活的方式:

const resolvers = { User: { email: (parent, _, context) => { // 只有本人或管理员能看到邮箱 if (context.user?.id !== parent.id && context.user?.role !== 'ADMIN') { return null; // 或抛出 ForbiddenError } return parent.email; }, salary: (parent, _, context) => { if (context.user?.role !== 'ADMIN') { throw new ForbiddenError('无权查看薪资信息'); } return parent.salary; } } }; ​

中间件/守卫层:在解析器执行前检查权限:

const authGuard = (next) => (root, args, context, info) => { if (!context.user) throw new AuthenticationError('未认证'); const requiredRole = info.parentType.getFields()[info.fieldName].extensions?.auth; if (requiredRole && context.user.role !== requiredRole) { throw new ForbiddenError(`需要 ${requiredRole} 权限`); } return next(root, args, context, info); }; ​

推荐的做法是:认证在context层统一处理,授权在解析器层按字段处理。Schema指令用于声明式地标记哪些字段需要认证,实际检查逻辑在指令的解析器实现中完成。

错误处理:GraphQL的部分执行模型

GraphQL的错误处理与REST有一个根本区别:GraphQL默认是部分执行的。即使部分字段出错,其他字段的数据仍然正常返回。

{ "data": { "user": { "name": "林远", "posts": [ { "title": "第一篇", "comments": null }, { "title": "第二篇", "comments": [/* ... */] } ] } }, "errors": [ { "message": "无法加载评论:服务暂时不可用", "locations": [{ "line": 7, "column": 9 }], "path": ["user", "posts", 0, "comments"] } ] } ​

这个设计是有意为之的——它让客户端能够最大化利用返回的数据,而不是因为一个字段出错就丢弃整个响应。

在Apollo Server中抛出错误的几种方式:

// 1. 业务错误(客户端可处理) throw new UserInputError('标题长度至少为5个字符', { invalidArgs: { title: '太短' } }); // 2. 认证错误 throw new AuthenticationError('请先登录'); // 3. 权限错误 throw new ForbiddenError('无权执行此操作'); // 4. 自定义错误(携带扩展信息) throw new GraphQLError('库存不足', { extensions: { code: 'INSUFFICIENT_STOCK', available: 3, requested: 5 } }); ​

通过 extensions 字段携带结构化的错误信息,让客户端能够根据错误码做特定处理,而不是解析错误消息字符串。

统一错误处理格式

在生产环境中,推荐定义统一的错误码体系和错误格式:

enum ErrorCode { AUTHENTICATION_REQUIRED FORBIDDEN INVALID_INPUT NOT_FOUND RATE_LIMITED INTERNAL_ERROR } class AppError extends GraphQLError { constructor( message: string, public code: ErrorCode, public statusCode: number, public details?: Record<string, any> ) { super(message, { extensions: { code, statusCode, details } }); } } // 错误格式化中间件 const formatError = (error: GraphQLError) => { const originalError = error.originalError as AppError; // 内部错误不暴露细节 if (!originalError || originalError.code === ErrorCode.INTERNAL_ERROR) { return { message: '服务器内部错误', code: 'INTERNAL_ERROR', path: error.path, }; } return { message: error.message, code: originalError.code, details: originalError.details, path: error.path, locations: error.locations, }; }; ​

这种统一格式让客户端的错误处理逻辑标准化:根据 code 字段判断错误类型,根据 details 获取具体信息,根据 path 定位出错的字段。

图:GraphQL错误响应结构

图:GraphQL错误响应结构

错误处理的设计权衡

GraphQL部分执行的特性既有优势也有风险:

优势:一个嵌套查询中,深层字段出错不会影响浅层字段的返回。客户端总能拿到尽可能多的数据。

风险:客户端必须处理"数据可能为null"的情况。如果客户端不做null检查,渲染时可能崩溃。

实践建议:

  • 用 ! 标记不允许为null的字段——如果解析器返回null,GraphQL引擎会把这个null向上传播
  • 客户端始终对列表和嵌套对象做null检查
  • 错误码体系标准化,避免客户端解析错误消息
  • 生产环境中屏蔽内部错误详情,避免信息泄露
作者与出处
原作者:灏天文库
来源:平台策划编纂
整理人: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U