⭐ 推荐:社区规则条款 V1.0

如何用 GraphQL 查询复杂度限制防止接口被采集?

liulian
liulian 初级会员萌新站长
发布于 2026-10-18 12:47 ·3 浏览 ·0 回复
内容摘要

防止 GraphQL 接口被采集,需要从按请求数限流改为按查询成本控制,组合深度上限、复杂度分析、分页参数硬上限和按成本计费的速率限制,超限直接拒绝;单靠 URL 限流或深度限制不足。

结论:防 GraphQL 接口被采集,单靠 URL 限流无效,必须对「查询本身」计量——用「深度上限 + 复杂度(成本)分析 + 分页参数强制上限 + 按成本计费的速率限制」四层组合,把单次查询成本卡在预算内(如 maxDepth=7、maxComplexity=1000、连接类字段 first≤100、单 IP 每分钟 5000 成本点),超限直接拒绝并返回错误码。

为什么单靠 URL 限流挡不住 GraphQL 采集?

因为 GraphQL 只有一个端点、一种方法(通常是 POST /graphql),请求体里的查询文本才是真正的「参数」,URL 层看到的永远是同一个地址。采集者把嵌套层数写深一点、把 first 写大一点,一个 HTTP 请求就能拉走 REST 需要上百次请求才能拿到的数据。结论:GraphQL 的限流粒度必须从「请求数」下沉到「查询成本」。

复杂度限制要分哪几层落地?

四层,缺一层都会漏:

  1. 深度限制:用 graphql-depth-limit,maxDepth 设 5–7(多数业务 schema 合法查询在 6 层以内)。它拦的是 user → posts → comments → author → posts → ... 这类环形嵌套。
  2. 成本/复杂度分析:用 graphql-query-complexity,给每个字段配 cost,列表字段按 first/last 参数相乘,设 maxComplexity: 1000。
  3. 分页硬上限:所有 connection 类型强制 first 必填且 ≤100,不传 first 直接报错。这一条最关键——没有它,一个 first: 10000 就能把成本算到几十万。
  4. 按成本计费的速率限制:不是「每分钟 100 次请求」,而是「每分钟 5000 成本点」,一次重查询可能直接吃掉 800 点。这样采集者要么慢下来,要么被打回。

复杂度计算怎么写?

以 Apollo Server 4 + graphql-query-complexity 为例,核心是给字段定义成本函数:

import { createComplexityRule } from 'graphql-query-complexity';

const complexityRule = createComplexityRule({
  maximumComplexity: 1000,
  variables: {},
  createError: (max, actual) =>
    new GraphQLError(`查询成本 ${actual} 超过上限 ${max}`),
  estimators: [
    fieldExtensionsEstimator(),      // 读取 schema 上的 cost 扩展
    simpleEstimator({ defaultComplexity: 1 }),
  ],
});

schema 里给重字段标注成本:posts: [Post] @cost(complexity: 10, multipliers: ["first"])。若 first: 100,该字段成本即 10×100=1000,单字段就顶到上限,采集者拿不到数据。结论:成本函数里必须把「列表字段 × 分页参数」作为乘法项,否则扁平但字段超多的查询会绕过深度限制。

只做深度限制为什么不够?

深度限制只看树的层数,不看宽度。一个 query { a1 a2 a3 ... a200 } 只有 2 层深,但会触发 200 个 resolver、200 次数据库查询,典型的「扁而宽」攻击。同理,user(id:1){...} user2:user(id:2){...} 这种别名堆叠,在查询文本里是同一个字段的多次调用。所以除了深度,还要加两条:单次查询别名数量上限(如 20) 和 同名字段重复次数上限,两者都在 validation 阶段拦。

还有哪些配套手段?

  • 生产环境关闭 introspection(introspection: false)并禁用 GraphQL Playground。采集者拿不到 schema,写深度查询的成本大幅上升。注意这只提高门槛,不构成防护。
  • 持久化查询/白名单:只允许客户端提交预先注册的 query hash(APQ 或 persisted queries),任意手写查询直接拒绝。这是对采集最狠的一招,代价是客户端发版流程要改。
  • 错误信息收敛:超限时统一返回 QUERY_TOO_COMPLEX,不要回显「你的成本是 8123,上限 1000」,否则等于把阈值告诉了对方。

阈值上线后怎么校准?

先用观测模式跑 7 天:不拦截,只记录每条查询的 depth 和 cost,取 P99 的 1.5–2 倍作为 maxComplexity,取 P99 向上取整到 8 作为 maxDepth。结论:阈值必须基于自己真实流量的 P99 定,抄别人的 1000 大概率误杀正常业务或漏掉真实攻击。上线后监控超限拒绝率,正常业务应低于 0.5%,超过说明阈值偏紧。

总结:GraphQL 防采集的本质是把「查询成本」变成一等公民——深度上限挡嵌套,成本分析挡宽查询,分页硬上限堵住放大器,按成本计费的限流让采集在经济上不划算,再叠加持久化查询白名单提高门槛。四层缺任何一层,另外三层都会被绕过。

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2265.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 0

还没有回复,来抢沙发~