背景与价值Webhook订阅需在引入前验证来源与控制权。挑战应答配合签名与窗口限制可有效防止伪造。统一规范来源白名单:仅接受受控域名的订阅请求。挑战应答:服务端生成挑战并要求客户端在窗口内签名返回。过期窗口:挑战在短窗口内有效,过期拒绝。核心实现挑战生成与校验function enc(s: string): Uint8Array { return new TextEncoder().encode(s) }
async function importHmac(secret: ArrayBuffer): Promise<CryptoKey> {
return crypto.subtle.importKey('raw', secret, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'])
}
async function hmac(key: CryptoKey, data: string): Promise<string> {
const raw = await crypto.subtle.sign('HMAC', key, enc(data))
const u = new Uint8Array(raw)
let s = ''
for (let i=0;i<u.length;i++) s += String.fromCharCode(u[i])
return btoa(s).replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,'')
}
type Challenge = { id: string; exp: number; nonce: string }
const store = new Map<string, Challenge>()
function now(): number { return Date.now() }
function genId(): string { return Math.random().toString(36).slice(2) }
function issueChallenge(ttlMs = 30000): Challenge {
const c: Challenge = { id: genId(), exp: now() + ttlMs, nonce: genId() }
store.set(c.id, c)
return c
}
async function verifyChallenge(id: string, sig: string, key: CryptoKey): Promise<boolean> {
const c = store.get(id)
if (!c) return false
if (now() > c.exp) return false
const expect = await hmac(key, c.nonce)
return expect === sig
}
落地建议为订阅请求下发挑战并要求在短窗口内返回HMAC签名,用以确认控制权。结合来源白名单与签名键治理,统一审计失败与异常请求。过期挑战统一清理并拒绝使用,避免重放与资源占用。验证清单挑战是否包含唯一ID与过期时间,签名是否匹配。是否按来源白名单与窗口限制实施门禁与审计。

发表评论 取消回复