适用场景
GraphQL API 正在快速替代传统 REST 接口,但它的单端点 + 任意嵌套查询特性带来了全新的攻击面。GraphQL API 安全防护适用于以下场景:使用 Apollo Server、GraphQL Yoga 或 Hasura 提供业务接口的团队;电商、SaaS 平台担心攻击者通过深度嵌套查询拖垮后端数据库;对接第三方开发者时需要限制其查询复杂度与调用频率。本教程面向后端开发与安全运维人员,帮助你在不牺牲功能的前提下封堵 GraphQL 特有攻击路径。
前置条件
- Node.js 16+ 环境,已创建 GraphQL 服务(本文以 Apollo Server 4 为例)
- 了解 GraphQL Schema、Query、Resolver 基本概念
- 可选:Nginx 反向代理环境用于网关层限流
原理说明
GraphQL 有三类典型攻击:深度查询攻击利用 Schema 中的关联字段无限嵌套(如查询用户的朋友的朋友……),使单个请求触发指数级数据库查询,耗尽 CPU 与连接池;批量查询攻击借助 __typename 别名机制在单请求内批量执行成百上千次相同查询,绕过传统按请求计数的限流;Introspection 探测通过 __schema 元查询完整导出 Schema,帮助攻击者精确绘制攻击面。防护思路分别是:限制查询深度与复杂度、按”操作数”而非”请求数”限流、在生产环境禁用 Introspection。
操作步骤
第一步:限制查询深度
安装并配置 graphql-depth-limit:
npm install graphql-depth-limit
// server.js
const depthLimit = require('graphql-depth-limit');
const { ApolloServer } = require('@apollo/server');
const server = new ApolloServer({
typeDefs, resolvers,
validationRules: [depthLimit(5)], // 最大嵌套深度 5 层
});
深层查询将直接返回校验错误,不进入 Resolver 执行。
第二步:限制查询复杂度
安装 graphql-validation-complexity 并定义成本系数:
npm install graphql-validation-complexity
const { createComplexityLimitRule } = require('graphql-validation-complexity');
const server = new ApolloServer({
typeDefs, resolvers,
validationRules: [
createComplexityLimitRule(1000, {
scalarCost: 1,
objectCost: 5,
listFactor: 10, // 列表字段按 10 倍计费
}),
],
});
高成本查询(如多级列表嵌套)会在执行前被拒绝,返回 “The query exceeds the maximum allowed complexity”。
第三步:生产环境禁用 Introspection
const server = new ApolloServer({
typeDefs, resolvers,
validationRules: [],
introspection: process.env.NODE_ENV !== 'production',
});
同时可增加网关层拦截,对 __schema / __type 字段请求直接返回 403:
# Nginx:阻止 Introspection 请求
if ($request_body ~* "__schema|__type") {
return 403;
}
第四步:按操作数限流(防批量别名攻击)
使用 @graphql-tools/utils 统计单请求内操作数并限流:
npm install graphql-rate-limit-directive
// 自定义中间件:统计顶层字段数量
const countOps = (query) => {
const matches = query.match(/\b[a-zA-Z_][a-zA-Z0-9_]*\s*\(/g);
return matches ? matches.length : 1;
};
// 在 context 中计数,超出阈值拒绝
app.use('/graphql', (req, res, next) => {
const ops = countOps(req.body?.query || '');
if (ops > 50) {
return res.status(429).json({ error: 'Too many operations in one request' });
}
next();
});
第五步:全局限流与并发控制
npm install rate-limit-redis ioredis
const { rateLimit } = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 60 * 1000, // 1 分钟窗口
max: 60, // 每 IP 最多 60 次
keyGenerator: (req) => req.ip,
standardHeaders: true,
});
app.use('/graphql', limiter);
配置验证
# 1. 深度限制验证:构造 10 层嵌套查询应返回校验错误
curl -X POST http://localhost:4000/graphql \
-H "Content-Type: application/json" \
-d '{"query":"query { user { friends { friends { friends { friends { friends { friends { id } } } } } } } }"}'
# 期望返回: "Anonymous query is too deep"
# 2. 复杂度限制验证:高成本查询应被拒绝
curl -X POST http://localhost:4000/graphql \
-H "Content-Type: application/json" \
-d '{"query":"query { users { posts { comments { id } } } }"}'
# 3. Introspection 验证:生产环境返回 403 或禁用
curl -X POST http://localhost:4000/graphql \
-H "Content-Type: application/json" \
-d '{"query":"{ __schema { types { name } } }"}'
# 4. 批量操作限流验证:单请求 100 个别名应返回 429
python3 -c "
import json, urllib.request
q = 'query { ' + ' '.join(['a%d: __typename' % i for i in range(100)]) + ' }'
data = json.dumps({'query': q}).encode()
req = urllib.request.Request('http://localhost:4000/graphql', data=data, headers={'Content-Type': 'application/json'})
try:
urllib.request.urlopen(req)
except urllib.error.HTTPError as e:
print('HTTP', e.code)
"
常见问题
FAQ 1:限制查询深度会影响正常业务吗?
会,需要权衡。深度限制 5 层对绝大多数业务足够,但复杂报表或”关注关系”类场景可能误伤。建议先开启日志模式(Apollo 的 didEncounterErrors 钩子)记录被拦截的查询,观察一周后再收紧;或对特定字段使用 @deprecated 引导客户端重构,而不是盲目调大深度上限。
FAQ 2:限流后恶意用户换 IP 绕过怎么办?
单一 IP 限流必然可绕过。生产环境应采用多层策略:按 API Key / 用户 Token 维度限流(Redis 分布式计数);对匿名流量叠加行为分析,识别高频”别名批量查询”特征;配合 CDN/WAF 层的 Bot 管理与设备指纹识别,阻断自动化攻击工具而非仅靠 IP。
总结
GraphQL 安全防护的核心是把”查询能力”量化:用深度限制约束嵌套层级,用复杂度评分约束资源消耗,用操作数限流封堵别名批量攻击,用 Introspection 关闭暴露攻击面。建议按”深度 → 复杂度 → 操作数 → 全局限流”四步逐步收紧,每步都配合日志观察误杀率,最终形成多层防线。网关层(Nginx/云 WAF)再叠加请求频率与 Bot 防护,即可有效抵御针对 GraphQL API 的深度查询与批量攻击。