---

title: JWT客户端断言与授权服务器认证(client_assertion)最佳实践

keywords:

  • client_assertion
  • RS256
  • jti
  • exp
  • iat
  • iss
  • sub
  • aud

description: 通过JWT客户端断言在令牌与授权端点认证客户端身份,校验iss/sub/aud/exp/iat/jti并验证签名,提升安全可信度。

categories:

  • 文章资讯
  • 技术教程

---

背景与价值

客户端断言使用私钥签名JWT以证明客户端身份,减少明文密钥传输风险并提升认证安全。

统一规范

  • 算法限定:RS256/ES256,拒绝弱算法与none
  • 字段一致:iss=sub=client_idaud 指向授权端点或令牌端点。
  • 时间与一次性:exp/iat窗口±300秒;jti一次性使用。

核心实现

断言验证

type Claims = { iss: string; sub: string; aud: string | string[]; exp: number; iat: number; jti: string }

function enc(s: string): Uint8Array { return new TextEncoder().encode(s) }
function b64u(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 importKey(spki: ArrayBuffer, type: 'RS256'|'ES256'): Promise<CryptoKey> { const algo = type==='RS256'?{ name:'RSASSA-PKCS1-v1_5', hash:'SHA-256'}:{ name:'ECDSA', namedCurve:'P-256' }; return crypto.subtle.importKey('spki', spki, algo as any, false, ['verify']) }

function timeNow(): number { return Math.floor(Date.now()/1000) }
function audOk(aud: string | string[], exp: string): boolean { return Array.isArray(aud) ? aud.includes(exp) : aud === exp }

class JtiStore { used = new Set<string>(); has(j: string) { return this.used.has(j) } add(j: string) { this.used.add(j) } }

async function verifyClientAssertion(jwt: string, key: CryptoKey, type: 'RS256'|'ES256', expected: { client_id: string; aud: string; leewaySec: number }, store = new JtiStore()): Promise<boolean> {
  const parts = jwt.split('.')
  if (parts.length !== 3) return false
  const [h,p,s] = parts
  const header = JSON.parse(new TextDecoder().decode(b64u(h)))
  if (header.alg !== type) return false
  const ok = await crypto.subtle.verify(type==='RS256'?{ name:'RSASSA-PKCS1-v1_5', hash:'SHA-256'}:{ name:'ECDSA', hash:'SHA-256' }, key, b64u(s), enc(h + '.' + p))
  if (!ok) return false
  const c: Claims = JSON.parse(new TextDecoder().decode(b64u(p)))
  if (c.iss !== expected.client_id || c.sub !== expected.client_id) return false
  if (!audOk(c.aud, expected.aud)) return false
  const now = timeNow()
  if (c.exp + expected.leewaySec < now) return false
  if (c.iat - expected.leewaySec > now) return false
  if (store.has(c.jti)) return false
  store.add(c.jti)
  return true
}

落地建议

  • client_assertion 作为客户端认证方式,授权端严格验签与字段校验。
  • 引入一次性 jti 存储与时间窗口容忍,阻断重放与时间漂移误判。

验证清单

  • iss/sub/aud/exp/iat/jti 是否一致且在窗口内;签名算法是否受控且验证成功。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部