我们内部核心的结算微服务上周又被脏数据搞崩溃了。起因是一个前端表单校验逻辑被恶意爬虫直接绕过,把一个包含负数金额和超长非法字符的订单 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 实时同步的?