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

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部