正文Webhook 是系统间事件通知的常用机制。为了保证来源可信与降低重放风险,需要在接收端实现签名校验与时间窗口控制。本文在 Next.js 15 的 Edge Runtime 下实现 HMAC-SHA256 验证与简单重放防护。一、签名数据结构与时间窗口上游以 `data = timestamp + '.' + body` 作为签名输入,`X-Timestamp` 记录毫秒时间戳,`X-Signature` 为 Base64 编码的 HMAC-SHA256。接收端验证时间窗口建议不超过 300 秒。二、Edge 路由实现export const runtime = 'edge' function toBase64(ab: ArrayBuffer) { const bytes = new Uint8Array(ab) let bin = '' for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]) return btoa(bin) } function constantTimeEqual(a: string, b: string) { if (a.length !== b.length) return false let r = 0 for (let i = 0; i < a.length; i++) r |= a.charCodeAt(i) ^ b.charCodeAt(i) return r === 0 } export async function POST(req: Request) { const ts = req.headers.get('x-timestamp') const sig = req.headers.get('x-signature') || '' if (!ts || !sig) return new Response(null, { status: 400 }) const now = Date.now() const t = Number(ts) if (!Number.isFinite(t) || Math.abs(now - t) > 300000) return new Response(null, { status: 408 }) const secret = process.env.WEBHOOK_SECRET || '' if (!secret) return new Response(null, { status: 500 }) const body = await req.text() const data = `${t}.${body}` const key = await crypto.subtle.importKey( 'raw', new TextEncoder().encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'] ) const mac = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(data)) const expect = toBase64(mac) if (!constantTimeEqual(expect, sig)) return new Response(null, { status: 401 }) return Response.json({ ok: true }, { headers: { 'Cache-Control': 'no-store' } }) } 三、治理建议秘钥管理:Edge 环境变量存放 `WEBHOOK_SECRET`,严格区分环境与最小化权限。重放防护:结合 `X-Idempotency-Key` 或一次性 nonce 记录到存储,窗口内仅接受首次请求。观测与审计:记录签名失败与窗口超时的计数指标并报警,结合 Reporting API 与 Server-Timing。

发表评论 取消回复