GraphQL API 安全防护实战:深度查询与批量攻击防御配置

适用场景

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 的深度查询与批量攻击。