正文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。

发表评论 取消回复