---

title: OAuth PAR与JAR请求对象签名(请求URI/签名验证)最佳实践

keywords:

  • PAR
  • JAR
  • 请求对象
  • RS256
  • 请求URI

description: 通过Pushed Authorization Requests与JWT-secured Authorization Request将授权参数推送并签名,保障授权请求的完整性与可信性。

categories:

  • 文章资讯
  • 技术教程

---

背景与价值

PAR与JAR可防止授权请求被篡改。推送后以请求URI引用并对请求对象验签,提升安全与可审计性。

统一规范

  • 推送参数:使用PAR将参数推送到授权服务器,返回 request_uri
  • JAR签名:对请求对象进行 RS256/ES256 签名并设置 exp/iataud/iss/client_id
  • 验证策略:资源端与授权端对请求对象进行验签与字段校验。

核心实现

请求对象生成与签名

type ReqObj = { iss: string; aud: string; client_id: string; redirect_uri: string; scope: string; response_type: string; iat: number; exp: number; state?: string; nonce?: string }

function enc(s: string): Uint8Array { return new TextEncoder().encode(s) }
function base64url(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(/=+$/,'') }

async function importPrivateKey(pkcs8: ArrayBuffer, type: 'RS256'|'ES256'): Promise<CryptoKey> { return crypto.subtle.importKey('pkcs8', pkcs8, type==='RS256'?{ name:'RSASSA-PKCS1-v1_5', hash:'SHA-256'}:{ name:'ECDSA', namedCurve:'P-256' }, false, ['sign']) }

async function signReqObj(obj: ReqObj, key: CryptoKey, type: 'RS256'|'ES256'): Promise<string> {
  const header = base64url(enc(JSON.stringify({ alg: type, typ: 'JWT' })))
  const payload = base64url(enc(JSON.stringify(obj)))
  const sig = await crypto.subtle.sign(type==='RS256'?{ name:'RSASSA-PKCS1-v1_5', hash:'SHA-256'}:{ name:'ECDSA', hash:'SHA-256' }, key, enc(header + '.' + payload))
  return header + '.' + payload + '.' + base64url(sig)
}

请求对象验证

async function importPublicKey(spki: ArrayBuffer, type: 'RS256'|'ES256'): Promise<CryptoKey> { return crypto.subtle.importKey('spki', spki, type==='RS256'?{ name:'RSASSA-PKCS1-v1_5', hash:'SHA-256'}:{ name:'ECDSA', namedCurve:'P-256' }, false, ['verify']) }
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 verifyReqObj(jwt: string, pub: CryptoKey, type: 'RS256'|'ES256', expected: { iss: string; aud: string; client_id: string; leewaySec: number }): Promise<ReqObj | null> {
  const [h,p,s] = jwt.split('.')
  if (!h || !p || !s) return null
  const header = JSON.parse(new TextDecoder().decode(buf(h)))
  if (header.alg !== type) return null
  const ok = await crypto.subtle.verify(type==='RS256'?{ name:'RSASSA-PKCS1-v1_5', hash:'SHA-256'}:{ name:'ECDSA', hash:'SHA-256' }, pub, buf(s), enc(h + '.' + p))
  if (!ok) return null
  const obj: ReqObj = JSON.parse(new TextDecoder().decode(buf(p)))
  const now = Math.floor(Date.now()/1000)
  if (obj.iss !== expected.iss || obj.aud !== expected.aud || obj.client_id !== expected.client_id) return null
  if (obj.exp + expected.leewaySec < now || obj.iat - expected.leewaySec > now) return null
  return obj
}

落地建议

  • 推送授权参数并使用请求URI引用,客户端与服务端对请求对象进行验签与窗口校验。
  • iss/aud/client_id/exp/iat/redirect_uri 等字段进行严格校验,拒绝不一致或过期请求。

验证清单

  • 请求对象是否成功验签且字段与时间窗口一致;PAR返回 request_uri 是否在有效期内。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部