这是「飞书对接」小专题的第4篇,共5篇,从《MCP实战手记(十一):MCP接飞书,从机器人到事件订阅的三层深度接入》延伸(对应正文第3层生产化 + 边界声明中的消息加解密与重放防护)。
篇11正文第3层讲生产化时提了一句"消息加解密与重放防护",又在第4节讲了event_id幂等。很多人把这两件事混为一谈:event_id幂等是防飞书自己重复投递,签名校验是防外部攻击者伪造/重放事件——完全是两回事。这篇一次讲清。
先分清:平台重投vs外部攻击重放
| 维度 | 平台重投(幂等) | 外部攻击重放 |
|---|---|---|
| 来源 | 飞书正常投递可能重复 | 攻击者截获/伪造事件再次发送 |
| 防御 | event_id去重(Redis SETNX) | 签名校验 + 时间窗 + nonce |
| 目的 | 避免同事件处理两次 | 避免伪造/过期事件被当真 |
| 位置 | 《飞书长连接事件订阅实战:个人本机5分钟跑通,生产要补两块》已讲 | 本篇 |
正文里的event_id去重解决的是"飞书好心投了两次",不解决"坏人假投一次"。后者才是安全话题。
飞书事件回调的签名校验
飞书给回调事件带签名头(含timestamp和nonce),用app_secret算签名比对,不一致就拒绝:
签名串 = timestamp + "\n" + nonce + "\n" + 加密/纯文本body expect = Base64(HmacSHA256(签名串, app_secret)) if (expect != header.x-lark-signature) -> 拒绝(401)关键点:
- 时间窗:
now - timestamp超过阈值(如5分钟)直接拒,防"旧事件被存下来以后重放"。 - nonce去重:同一
nonce短时间内出现两次即可疑,配合时间窗挡重放。 - 签名密钥是
app_secret,绝不下发到客户端、绝不进日志。
// 签名校验(伪代码,落到handler最前面)Stringraw=timestamp+"\n"+nonce+"\n"+body;Stringexpect=Base64.getEncoder().encodeToString(hmacSha256(raw.getBytes(UTF_8),appSecret.getBytes(UTF_8)));if(!expect.equals(signatureHeader))return401;// 伪造,拒if(Math.abs(System.currentTimeMillis()/1000-Long.parseLong(timestamp))>300)return401;// 过期,拒(重放窗口)if(!redis.setnx("nonce:"+nonce,"1",Duration.ofMinutes(5)))return401;// 重放,拒消息加解密:encrypt_key解密
如果应用开了"事件加密",飞书发来的body是密文,要用后台配置的encrypt_key解密才能拿到真实事件:
- 取
encrypt字段 → 用encrypt_key做AES解密 → 得到明文JSON。 - 解密失败(key不对 / 被篡改)直接拒。
encrypt_key同属密钥,调试完按正文第8层"密钥已重置声明"处理:重置 + 删本地含密钥文件。
三件必须一起做的安全动作
- 验签:确认事件真来自飞书(防伪造)。
- 时间窗 + nonce去重:确认事件没过期、没被重放(防重放)。
event_id幂等:确认平台没重复投递(防重复处理,散点文01)。
三者职责不重叠:1+2 挡外部攻击,3 挡平台自身的正常重复。漏掉任何一层都会在特定场景下翻车。
三个坑
坑一:只验event_id不做验签。那是防重投不是防伪造,攻击者能直接构造合法event_id的假事件打进来。
坑二:时间窗设太长。窗口越大,重放能存活越久。5分钟是飞书常见默认,别随手改成1小时。
坑三:密钥进日志/仓库。app_secret/encrypt_key一旦泄露,验签形同虚设。用环境变量注入,发布前按正文"密钥已重置声明"重置并清理本地文件。
小结
事件安全 = 验签(防伪造)+ 时间窗/nonce(防重放)+event_id幂等(防平台重投)。前两件是安全,后一件是健壮性,别混。长连接把接入变简单,但安全动作一个都不能省——尤其密钥管理,是读者最容易翻车、也最容易掉信任的地方。
同专题推荐
本文是「飞书对接」小专题的第4篇,共5篇:
- 飞书长连接事件订阅实战:个人本机5分钟跑通,生产要补两块
- 飞书卡片消息构建实战:从文本到可交互富卡片
- 飞书用户身份与多租户数据隔离:店长只看自己门店
- 飞书事件安全:签名校验与消息加解密(← 当前这篇)
- 钉钉接入实战与平台横向差距(链接待补)
相关阅读
- 《MCP实战手记(十一):MCP接飞书,从机器人到事件订阅的三层深度接入》
- 《MCP实战手记系列(九):生产化 MCP Server 的6层安全防护》(6层防护:mTLS/身份/RBAC/审计)
- 《MCP实战手记系列(八):无状态之后的生产落地清单》(Server 无状态可水平扩展)
- 《MCP实战手记系列(十):MCP Server高可用实战》(6工具复用 StoreService)
- 配套代码:https://gitee.com/ethanliang2016/mcp-in-action(模块
11-feishu,tagv11)