### [🛡️ PHP 接口防护实战:GraphQL 查询深度限制与字段选取防护](https://www.huociguo.com/article/205) **Published:** 2026-07-19T13:30:17 **Author:** 米了 **Excerpt:** 一条没带防护的 GraphQL 接口,恶意嵌套查询 30 秒就能把数据库拖垮。GraphQL 把查询自由度交给了客户端,爽了前端,也把风险敞口留给了后端。这篇把 PHP 生态下最实用的防护方案一次讲透,代码可直接落地。 没防护的接口等于裸奔 一条没带防护的 GraphQL 接口,恶意嵌套查询 30 秒就能把数据库拖垮。GraphQL 把查询自由度交给了客户端,爽了前端,也把风险敞口留给了后端。这篇把 PHP 生态下最实用的防护方案一次讲透,代码可直接落地。 ![](https://api.huociguo.com/wp-content/uploads/2026/07/PHP%E6%8E%A5%E5%8F%A3%E9%98%B2%E6%8A%A4%E5%AE%9E%E6%88%98%EF%BC%9AGraphQL%E6%9F%A5%E8%AF%A2%E6%B7%B1%E5%BA%A6%E9%99%90%E5%88%B6%E4%B8%8E%E5%AD%97%E6%AE%B5%E9%80%89%E5%8F%96%E9%98%B2%E6%8A%A4-1913f4cf5d-1.png "PHP接口防护实战:GraphQL查询深度限制与字段选取防护-1913f4cf5d-1") **没防护的接口等于裸奔** GraphQL 一个入口查所有数据,风险集中在三点: - 🔗 嵌套无上限:user → friends → friends → friends……每多一层,后端数据加载量指数级膨胀 - 🌊 字段全选零成本:一条查询把几十张表的字段全拉一遍,数据库瞬间高负载 - 🗺️ 内省暴露全部结构:一条 `__schema` 查询,所有类型、字段、参数一览无余,攻击面完全透明 这类攻击门槛极低,Postman 手写一条嵌套查询即可复现。 **第一道闸门:查询深度限制** PHP 生态主流的 GraphQL 服务端实现是 `webonyx/graphql-php`,库内置了安全规则 `QueryDepth`,但**默认不启用**,必须手动挂上: ```php use GraphQL\Validator\DocumentValidator; use GraphQL\Validator\Rules\QueryDepth; DocumentValidator::addRule(new QueryDepth(8)); ``` 一行代码,所有查询在执行前先过校验,嵌套超过 8 层直接拒绝并返回错误,数据库完全不受波及。 深度设多少?常规业务 5~8 层足够。真实业务里极少出现三层以上的关联查询,阈值放太宽等于没设。 **第二道闸门:查询复杂度计分** 只限深度有漏洞:浅层但宽得离谱的查询照样打爆数据库,比如一次拉 1 万个用户的完整信息。这时候上 `QueryComplexity`: ```php use GraphQL\Validator\Rules\QueryComplexity; DocumentValidator::addRule(new QueryComplexity(200)); ``` 规则给每个字段计 1 分,总分超限即拒。关键在于列表字段的计分逻辑——按分页参数动态放大: ```php 'users' => [ 'type' => Type::listOf($userType), 'args' => [ 'first' => ['type' => Type::int(), 'defaultValue' => 20], ], 'complexity' => fn(int $childrenComplexity, array $args): int => max(1, $args['first']) * $childrenComplexity, 'resolve' => fn($root, array $args) => UserRepository::paginate(min($args['first'], 100)), ], ``` `first=100` 时,该字段成本 = 100 × 子字段分数。恶意批量拉取一提交,复杂度立刻爆表被拦。 📌 深度限制管”钻多深”,复杂度限制管”铺多宽”,两道闸门缺一不可。 **第三道闸门:字段选取防护** 查询规模受控之后,字段本身还分三六九等。手机号、身份证号、薪资、内部备注这类字段,必须做字段级权限隔离: ```php 'salary' => [ 'type' => Type::float(), 'resolve' => function (User $user, array $args, AppContext $context) { if (!$context->isAdmin()) { return null; } return $user->salary; }, ], ``` 三条铁律: 1. 敏感字段从一开始就别写进 Schema,物理隔离最彻底 2. 必须暴露的字段,权限判断写进 resolve,无权返回 `null`,绝不在报错里泄露字段存在性 3. 权限逻辑统一收进 Context 对象,不要散落到每个 resolve 各写一套 **生产环境必须关掉内省** 内省(Introspection)是开发期的好帮手,上线后就是攻击者的地图。生产环境直接禁用: ```php use GraphQL\Validator\Rules\DisableIntrospection; if (getenv('APP_ENV') === 'production') { DocumentValidator::addRule(new DisableIntrospection()); } ``` 关掉之后 `__schema`、`__type` 查询一律报错,Schema 结构不再对外透明。开发环境保持开启,GraphiQL 调试不受影响。 **别漏了分页上限与 N+1** 两道附加题,漏做照样出事: - 📄 分页参数封顶:`first`、`last` 强制上限 100,resolve 里用 `min()` 兜底,客户端传值永远不可信 - 🔁 N+1 查询:列表场景上 DataLoader 批量加载,否则 100 条数据就是 101 次 SQL,可搭配 `overblog/dataloader` 或自行实现缓冲合并 **完整配置,直接抄作业** ```php toArray(); header('Content-Type: application/json; charset=utf-8'); echo json_encode($result, JSON_UNESCAPED_UNICODE); ``` Laravel 项目用 Lighthouse 更省事:`config/lighthouse.php` 的 `security` 配置项直接设置 `max_depth`、`max_complexity`、`disable_introspection`,无需手动注册。 **总结**:深度限制管嵌套、复杂度计分管规模、字段权限管内容、关内省管地图。四件套配齐,GraphQL 接口才算真正能上线 ✅ **Tags:** API防护, GraphQL, PHP, PHP教程, webonyx, 后端开发, 接口安全, 程序员日常 **Categories:** PHP教程 ---