背景与价值JWKS公钥轮换要求资源端快速适配。缓存TTL与回退策略可降低失败概率并保障验签稳定。统一规范域白名单:只允许受控 `jwks_uri` 域名。TTL上限:缓存不超过上限(如30分钟)且不超过响应提示。回退策略:当前kid缺失时尝试刷新,失败则回退至上一版并记录审计。核心实现JWKS抓取与缓存type Jwk = { kid: string; kty: string; crv?: string; n?: string; e?: string } type Jwks = { keys: Jwk[] } class Cache<T> { data = new Map<string, { v: T; until: number }>(); get(k: string): T | undefined { const e = this.data.get(k); if (!e) return; if (Date.now() > e.until) { this.data.delete(k); return } return e.v } set(k: string, v: T, ttlMs: number) { this.data.set(k, { v, until: Date.now() + ttlMs }) } } const allowOrigins = new Set(['https://auth.example.com']) function originAllowed(url: string): boolean { try { const u = new URL(url); return allowOrigins.has(u.origin) } catch { return false } } async function fetchJwks(uri: string): Promise<Jwks | null> { if (!originAllowed(uri)) return null const r = await fetch(uri, { headers: { 'accept': 'application/json' } }) if (!r.ok) return null return r.json() } 验签与回退function base64urlToBuf(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 importRsa(nB64: string, eB64: string): Promise<CryptoKey> { const jwk: JsonWebKey = { kty: 'RSA', n: nB64, e: eB64, ext: true } return crypto.subtle.importKey('jwk', jwk, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, false, ['verify']) } type JwtHeader = { alg: 'RS256' | 'ES256'; kid?: string } type VerifyCtx = { jwksUri: string; cache: Cache<Jwks>; maxTtlMs: number } async function verifyJwt(jwt: string, ctx: VerifyCtx): Promise<boolean> { const parts = jwt.split('.') if (parts.length !== 3) return false const [h, p, s] = parts const header: JwtHeader = JSON.parse(new TextDecoder().decode(base64urlToBuf(h))) if (header.alg !== 'RS256') return false let jwks = ctx.cache.get(ctx.jwksUri) let triedRefresh = false async function ensureJwks(): Promise<Jwks | null> { if (jwks) return jwks const fresh = await fetchJwks(ctx.jwksUri) if (!fresh) return null ctx.cache.set(ctx.jwksUri, fresh, ctx.maxTtlMs) jwks = fresh triedRefresh = true return fresh } jwks = jwks || await ensureJwks() if (!jwks) return false let key = jwks.keys.find(k => k.kid === header.kid && k.kty === 'RSA') if (!key && !triedRefresh) { jwks = await ensureJwks(); key = jwks?.keys.find(k => k.kid === header.kid && k.kty === 'RSA') || undefined } if (!key || !key.n || !key.e) return false const pub = await importRsa(key.n, key.e) const sig = base64urlToBuf(s) const ok = await crypto.subtle.verify({ name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, pub, sig, new TextEncoder().encode(h + '.' + p)) return !!ok } 落地建议对 `jwks_uri` 实施域白名单;缓存TTL不超过上限与响应提示。当kid缺失或不匹配时触发刷新与回退审计,失败拒绝访问。验证清单缓存是否在TTL内命中;kid是否精确匹配;失败是否触发刷新与回退。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部
1.672905s