背景与价值PAR与JAR可防止授权请求被篡改。推送后以请求URI引用并对请求对象验签,提升安全与可审计性。统一规范推送参数:使用PAR将参数推送到授权服务器,返回 `request_uri`。JAR签名:对请求对象进行 `RS256/ES256` 签名并设置 `exp/iat` 与 `aud/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` 是否在有效期内。

发表评论 取消回复