我们内部核心的结算微服务上周又被脏数据搞崩溃了。起因是一个前端表单校验逻辑被恶意爬虫直接绕过,把一个包含负数金额和超长非法字符的订单 Payload 怼进了内网。因为历史包袱太重,底层的 Go 微服务在接收到请求后,硬编码的业务校验漏掉了极端边界值,导致数据库底层算账溢出,触发了连锁雪崩。
天天去吐槽业务开发写 Bug 解决不了根本问题。把防御逻辑后退到微服务本身,不仅心智负担极重,而且不同开发人员写出来的校验标准千奇百怪。我们一合计,干脆把大门关紧,在流量入口的 API 网关层(我们用的是 APISIX)直接做请求 Payload 的强验证。只要是不符合 Swagger/OpenAPI 定义的脏数据,连进入我们内网微服务的机会都没有,直接在网关层弹回 400。
APISIX 内置的 request-validation 插件非常适合干这个事情。它可以直接接收标准的 JSON Schema 定义。我们通过自动化脚本,将开发团队维护的 OpenAPI 定义文档定期提取出对应的 Schema 参数定义,然后通过 Admin API 动态同步更新到网关路由中。
以下是我们生产环境配置的一条典型 APISIX 路由规则,展示了如何配置 request-validation 插件来拦截不符合规范的下单请求:
{ "uri": "/api/v1/orders", "name": "create-order-route", "methods": ["POST"], "plugins": { "request-validation": { "body_schema": { "type": "object", "required": ["userId", "items", "paymentMethod"], "properties": { "userId": { "type": "integer", "minimum": 1 }, "items": { "type": "array", "minItems": 1, "items": { "type": "object", "required": ["productId", "quantity", "price"], "properties": { "productId": { "type": "string", "pattern": "^prod_[a-zA-Z0-9]{16}$" }, "quantity": { "type": "integer", "minimum": 1, "maximum": 99 }, "price": { "type": "number", "minimum": 0.01 } }, "additionalProperties": false } }, "paymentMethod": { "type": "string", "enum": ["wechat", "alipay", "credit_card"] } }, "additionalProperties": false }, "rejected_code": 400, "rejected_msg": "请求参数不合法,请检查传入结构是否符合 OpenAPI 规范定义" }, "proxy-rewrite": { "headers": { "set": { "X-Gateway-Validated": "true" } } } }, "upstream": { "nodes": { "10.0.8.25:8080": 1 }, "type": "roundrobin" }}配置上线之后,我们做过一轮压测。网关层解析 JSON Schema 确实会带来一定的 CPU 损耗,在 QPS 到达 5000 以上时,网关的响应时延(Latency)大概增加了 1.8ms。但相比于脏数据引发的核心微服务数据库慢查询甚至雪崩,这点性能开销完全在接受范围内。
另外一个比较大的坑是网关的版本兼容性。有时候后端微服务发布新版本,字段还没在网关更新,前端发来的请求就全被拦截了,所以在 CI/CD 流程中必须把“网关 Schema 刷新”作为热发布的一环。
大家线上有在网关层做强约束验证吗?如果有大规模的 API 变动,你们是如何保证 API 文档和网关 Schema 实时同步的?