GraphQL 安全有哪些最佳实践?
GraphQL 安全有哪些最佳实践?
GraphQL 的灵活查询机制在带来便利的同时,也引入了 REST 所没有的安全风险。面试中高频考察的核心问题是:如何防止恶意查询拖垮服务,以及如何控制数据访问边界。
一、防攻击层:限制查询能力
GraphQL 允许客户端自由组合查询,这使 DoS 攻击变得容易。防护手段分三层:
查询深度限制——防止无限嵌套:
javascriptconst depthLimit = require('graphql-depth-limit'); const server = new ApolloServer({ typeDefs, resolvers, validationRules: [depthLimit(7)] });
查询复杂度限制——按字段权重计算总分,超标直接拒绝:
javascriptconst { createComplexityLimitRule } = require('graphql-validation-complexity'); const server = new ApolloServer({ validationRules: [createComplexityLimitRule(1000)] });
速率限制——限制单位时间内的请求次数:
javascriptconst rateLimit = require('express-rate-limit'); app.use('/graphql', rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));
追问:三层防护各自的适用场景?深度限制针对递归嵌套,复杂度针对广度展开,速率限制针对高频请求。三者互补,缺一不可。
二、认证授权层:控制数据访问
认证放在 context 中统一处理,授权下沉到 resolver 逐字段控制:
javascriptconst server = new ApolloServer({ context: ({ req }) => { const token = req.headers.authorization || ''; try { return { user: jwt.verify(token, process.env.JWT_SECRET) }; } catch { return { user: null }; } } });
字段级权限用指令声明,resolver 中校验:
graphqldirective @auth(requires: Role) on FIELD_DEFINITION type User { email: String! @auth(requires: ADMIN) salary: Float @auth(requires: ADMIN) }
追问:为什么不能只在入口做授权?因为 GraphQL 的字段级组合查询可以绕过接口级鉴权,用户可能通过合法入口请求到未授权的敏感字段。
三、输入安全层:防注入与验证
永远不要拼接 SQL,用参数化查询或 ORM:
javascript// 错误:字符串拼接 const query = `SELECT * FROM users WHERE id = '${userId}'`; // 正确:参数化查询 const query = 'SELECT * FROM users WHERE id = ?';
输入验证用 Yup 或 GraphQL Schema 约束指令双重保障,reject 不合规输入。
四、运维安全层:日志与错误处理
生产环境必须做到两点:错误信息脱敏(不暴露堆栈和内部结构),查询日志审计(记录 operationName 和变量,监控异常模式)。
javascriptformatError: (error) => { if (process.env.NODE_ENV === 'production') { return new Error('服务器内部错误'); } return error; }
同时禁用生产环境的 introspection 和 GraphQL 调试工具,防止 schema 泄露。
追问:introspection 禁用后如何提供文档?用代码生成工具从 schema 导出静态文档,开发环境保留 introspection,生产关闭。
五、CORS 与查询白名单
CORS 只允许可信域名访问;持久化查询(Persisted Queries)只允许预注册的查询通过,从源头阻断任意查询执行。
以上五层从前到后形成纵深防御:先限流、再鉴权、再验证输入、再脱敏输出、最后收窄查询入口。实际项目中按优先级逐步落地,不必一步到位。