---
title: HTTP消息签名与验证(Signature-Input/Signature)最佳实践
keywords:
- HTTP Message Signatures
- Signature-Input
- Signature
- 组件列表
- ES256
description: 基于Signature-Input与Signature头的HTTP消息签名与验证,通过组件列表与规范化拼接、ES256签名与窗口校验,保障请求完整性与不可抵赖性。
categories:
- 文章资讯
- 技术教程
---
背景与价值
HTTP消息签名以标准化组件列表构建签名基串并下发签名头,资源端可验证消息完整性与来源可信度,降低重放与篡改风险。
统一规范
- 组件列表:明确签名包含的头、方法与路径(如
@method、@path、content-digest)。 - 算法限定:采用
ES256;keyid标识公钥并支持轮换。 - 时间窗口:可选
created/expires提供短期有效窗口。
核心实现
组件与基串构建
type Req = { method: string; path: string; headers: Record<string, string | undefined> }
type Component = '@method' | '@path' | string // header name in lower-case
function h(req: Req, k: string): string { return (req.headers[k] || '').trim() }
function buildBase(req: Req, components: Component[]): string {
const lines: string[] = []
for (const c of components) {
if (c === '@method') lines.push(`"@method": ${req.method.toLowerCase()}`)
else if (c === '@path') lines.push(`"@path": ${req.path}`)
else lines.push(`"${c}": ${h(req, c)}`)
}
return lines.join('\n')
}
function buildSignatureInput(components: Component[], params: { alg: 'ES256'; keyid: string; created?: number; expires?: number }): string {
const items = components.map(c => c).join(' ')
const p: string[] = [`alg=${params.alg}`, `keyid="${params.keyid}"`]
if (params.created) p.push(`created=${params.created}`)
if (params.expires) p.push(`expires=${params.expires}`)
return `(${items});` + p.join('; ')
}
签名与验证(ES256)
function enc(s: string): Uint8Array { return new TextEncoder().encode(s) }
function b64u(b: ArrayBuffer): string { const u = new Uint8Array(b); let s=''; for (let i=0;i<u.length;i++) s+=String.fromCharCode(u[i]); return btoa(s).replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,'') }
function buf(s: string): ArrayBuffer { const b = atob(s.replace(/-/g,'+').replace(/_/g,'/')); const u = new Uint8Array(b.length); for (let i=0;i<b.length;i++) u[i]=b.charCodeAt(i); return u.buffer }
async function importPrivateKey(pkcs8: ArrayBuffer): Promise<CryptoKey> { return crypto.subtle.importKey('pkcs8', pkcs8, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['sign']) }
async function importPublicKey(spki: ArrayBuffer): Promise<CryptoKey> { return crypto.subtle.importKey('spki', spki, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']) }
async function signBase(base: string, key: CryptoKey): Promise<string> { const raw = await crypto.subtle.sign({ name: 'ECDSA', hash: 'SHA-256' }, key, enc(base)); return b64u(raw) }
async function verifyBase(base: string, sigB64u: string, key: CryptoKey): Promise<boolean> { return crypto.subtle.verify({ name: 'ECDSA', hash: 'SHA-256' }, key, buf(sigB64u), enc(base)) }
窗口校验
function timeNow(): number { return Math.floor(Date.now()/1000) }
function within(created?: number, expires?: number, leeway = 300): boolean { const n = timeNow(); if (created && created - leeway > n) return false; if (expires && expires + leeway < n) return false; return true }
落地建议
- 在请求包含
Signature-Input与Signature;组件与参数明确,启用时间窗口。 - 资源端按组件构建基串并验签,校验
created/expires与keyid轮换策略。
验证清单
- 组件列表是否正确;签名算法与基串校验是否通过;时间窗口是否有效。

发表评论 取消回复