AI 网关模型调用方签名校验与防重放攻击落地

日期:2026-08-21

一、项目背景

我们网关公网暴露之后,月初账单异常飙高,财务那边甩过来一张图,某调用方当天调用量是平时的三十倍,但业务侧说没发那么多请求。我们查网关日志才发现,有人抓包拿到了调用方的请求,然后原样重放,白嫖我们的算力。更尴尬的是,那个被冒用的调用方密钥就明文躺在配置文件里,git 仓库里都能翻到。这件事给我们上了一课:网关一旦公网暴露,鉴权不能只靠一个静态 key,请求本身得能证明"是我发的、且没被别人重发过"。

二、落地场景

我们落地的方案是"签名加防重放加密钥安全存储"三件套。第一块是请求签名,调用方用密钥对"请求体加时间戳加 nonce"做 HMAC 签名,网关侧验签通过才放行,密钥不出现在请求明文里。第二块是防重放窗口,网关记录近期用过的 nonce,在时间窗口内重复出现的请求直接拒绝,攻击者重放抓到的包会被识别。第三块是密钥加密存储,调用方密钥从明文配置迁到加密密钥库,应用启动时拉取,日志和仓库里不再出现明文。第四块是异常调用方封禁,对短时间内签名异常或重放命中率高的调用方自动限流或封禁。第五块是风控联动,封禁事件推给风控看板,人工可介入解封。

三、关键技术挑战与解决思路

时钟漂移是签名校验最隐蔽的坑。我们第一版要求时间戳和网关时间误差不超过三十秒,结果有个调用方服务器时钟慢了五分钟没发现,它的所有请求都被判签名过期,业务直接挂了,排查花了半天。后来改成"允许正负两分钟漂移加服务端时间兜底",并对调用方时钟做健康检查,漂移超阈预警而不是硬拒。nonce 去重存储也是个难题:窗口设太短防不住重放,设太长存储压力大。我们用了个带 TTL 的分布式缓存存 nonce,TTL 等于防重放窗口,过期自动清,存储量和窗口长度挂钩可控。

密钥迁移当时阻力不小,十几个调用方都得改代码从密钥库拉密钥,我们做了兼容期:旧明文 key 和新签名同时支持一段时间,逐步切换,避免一刀切把所有调用方逼停。封禁误伤也发生过,有个调用方因为网络抖动短时间重发了几条(其实是客户端重试),被我们误封,后来加了一条"重放命中率超阈值且持续才封禁",偶发重试不再误伤。

四、效果数据

签名加防重放上线后,重放请求拦截率约百分之百(窗口内重放全部拒绝)。异常账单事件从每月数次降到零,月初账单异常飙高的问题彻底消失。密钥明文暴露点清零,所有调用方密钥迁入加密密钥库。误封率从上线初期的约百分之二降到约千分之一以内,偶发客户端重试不再触发封禁。风控联动封禁的异常调用方,平均处置时效从人工发现的小时级压到自动封禁的秒级。

案例片段(已脱敏): 网关鉴权配置:sign.algo = HMAC-SHA256,timestamp.skew = 120s,nonce.ttl = 120s(TTL 缓存),replay_window = 120s,ban.threshold = 重放命中率 > 30% 且持续 60s。监控记录某调用方 10 分钟内出现 1.2 万次 nonce 命中(重放),命中率 98%,触发自动封禁并推送风控。排查为该调用方密钥此前明文泄露,已轮换密钥并迁入密钥库,解封后无再发。

密钥库的选型我们纠结过一阵,最初想直接用现成的配置中心存,但配置中心默认会把值打在审计日志里,和"不出现明文"的初衷冲突。最后选了带加密存储和访问控制、且日志脱敏的专用密钥库,应用启动拉取、内存持有、不落盘不打印。密钥轮换流程我们也固化了:泄露发生后,旧密钥标记失效、新密钥并行生效一段重叠期、调用方在重叠期内完成切换、旧密钥才彻底吊销,避免轮换本身造成大面积中断。这套流程跑顺之后,我们甚至把轮换做成了定期主动动作,不等泄露才换。风控联动那块,封禁事件推到看板后,我们发现有几次是调用方自己代码 bug 疯狂重试导致的"伪攻击",这类我们在风控侧加了白名单豁免,不误伤正常但写得糙的调用方,安全和体验之间还是得留点余地。

五、可复用经验总结

网关公网暴露,静态 key 等于把门钥匙挂在门上,签名加防重放是基本功,我们被刷过一次账单才彻底把这事当回事,代价其实完全可以提前花。时钟漂移这种问题上线前很难想到,但它能把正常调用方误伤到业务挂掉,服务端时间兜底加健康检查比硬拒稳。nonce 去重用带 TTL 的缓存是最省心的做法,窗口和存储量天然绑定,不用自己写清理。密钥明文这事我现在看是低级错误但很常见,凡是有密钥的地方第一反应就该是"它会不会出现在日志或仓库里"。封禁规则要防误伤,偶发重试和真实攻击的差别就在命中率和持续性,阈值卡准了才不会冤枉好人。这套签名防重放后来成了我们网关的默认开启项,新接入的调用方强制要走。