背景与价值 DPoP通过绑定客户端密钥与请求上下文,降低令牌被盗后滥用风险。严格验证字段与重放窗口可保障持有者证明生效。 统一规范 - 字段校验:`htu` 与 `htm` 必须与当前请求完全匹配。 - 重放防护:`jti` 一次性使用,`iat` 提供±300秒容忍窗口。 - 令牌绑定:访问令牌 `cnf.jkt` 必须与DPoP公钥拇指指对应。 核心实现 DPoP验证 ```ts type Req = { method: string; url: string; headers: Record } type DpopClaims = { htu: string; htm: string; jti: string; iat: number; cnf?: { jkt?: string } } function base64urlToBuf(s: string): ArrayBuffer { const b = atob(s.replace(/-/g,'+').replace(/_/g,'/')); const u = new Uint8Array(b.length); for (let i=0;i { return crypto.subtle.importKey('jwk', jwk, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']) } function sha256Hex(buf: ArrayBuffer): Promise { return crypto.subtle.digest('SHA-256', buf).then(d => { const u = new Uint8Array(d); let s = ''; for (let i=0;i(); has(j: string) { return this.used.has(j) } add(j: string) { this.used.add(j) } } async function verifyDpop(dpop: string, req: Req, jwk: any, expectedJkt?: string, leewaySec = 300, store = new JtiStore()): Promise { const parts = dpop.split('.') if (parts.length !== 3) return false const [h, p, s] = parts const header = JSON.parse(new TextDecoder().decode(base64urlToBuf(h))) if (header.typ !== 'dpop+jwt') return false if (header.alg !== 'ES256') return false const key = await importDpopKey(header.jwk) const sig = base64urlToBuf(s) const ok = await crypto.subtle.verify({ name: 'ECDSA', hash: 'SHA-256' }, key, sig, new TextEncoder().encode(h + '.' + p)) if (!ok) return false const claims: DpopClaims = JSON.parse(new TextDecoder().decode(base64urlToBuf(p))) const url = new URL(req.url, 'https://app.example.com') const htu = url.origin + url.pathname if (claims.htm.toUpperCase() !== req.method.toUpperCase()) return false if (claims.htu !== htu) return false if (store.has(claims.jti)) return false const now = timeNow() if (claims.iat - leewaySec > now) return false if (claims.iat + leewaySec < now) return false const jwkThumb = await sha256Hex(await crypto.subtle.exportKey('raw', key)) if (expectedJkt && expectedJkt !== jwkThumb) return false store.add(claims.jti) return true } ``` 令牌绑定(cnf.jkt)示例 ```ts type AccessToken = { cnf?: { jkt?: string } } function tokenBound(ok: boolean, token: AccessToken, jkt: string): boolean { return ok && token.cnf?.jkt === jkt } ``` 落地建议 - 在资源服务器验证DPoP并绑定访问令牌的 `cnf.jkt`,拒绝未绑定或不匹配请求。 - 维护 `jti` 一次性使用存储与合理窗口,阻断重放与时钟偏移误判。 验证清单 - `htu/htm` 是否与当前请求完全匹配;`jti/iat` 是否通过窗口与一次性校验。 - `cnf.jkt` 是否与DPoP公钥拇指指一致。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部