背景与价值授权码拦截与重放可导致令牌被盗。PKCE(S256)与 `state/nonce` 联动以及 `redirect_uri` 白名单能有效降低风险,适用于移动端与SPA。统一规范方法限定:仅允许 `S256`,禁止 `plain`。验证器长度与字符集:`code_verifier` 长度 43-128,字符集 `[A-Za-z0-9-._~]`。重定向白名单:`https` 域名与路径严格匹配,必要时仅允许 `http://localhost` 开发例外。关联校验:`state` 与会话绑定,`nonce` 与ID Token匹配。核心实现PKCE校验function b64url(bytes: ArrayBuffer): string { const u = new Uint8Array(bytes) let s = '' for (let i = 0; i < u.length; i++) s += String.fromCharCode(u[i]) return btoa(s).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/,'') } function validVerifier(v: string): boolean { return /^[A-Za-z0-9\-\._~]{43,128}$/.test(v) } async function s256(v: string): Promise<string> { const d = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(v)) return b64url(d) } async function pkceOk(verifier: string, challenge: string, method: string): Promise<boolean> { if (method !== 'S256') return false if (!validVerifier(verifier)) return false const calc = await s256(verifier) return calc === challenge } 重定向URI白名单const allowRedirects = new Set([ 'https://app.example.com/callback', 'https://mobile.example.com/auth/callback', 'http://localhost:3000/callback' ]) function redirectAllowed(uri: string): boolean { try { const u = new URL(uri) if (u.protocol === 'http:' && u.hostname !== 'localhost') return false return allowRedirects.has(u.origin + u.pathname) } catch { return false } } state/nonce关联与回调校验type Session = { state: string; nonce: string } const sessionById = new Map<string, Session>() function putSession(id: string, s: Session) { sessionById.set(id, s) } function getSession(id: string): Session | undefined { return sessionById.get(id) } type Callback = { code: string; state: string; redirect_uri: string; code_verifier: string; code_challenge: string; code_challenge_method: string; id_token_nonce?: string } async function verifyCallback(sessId: string, cb: Callback): Promise<boolean> { const sess = getSession(sessId) if (!sess) return false if (cb.state !== sess.state) return false if (cb.id_token_nonce && cb.id_token_nonce !== sess.nonce) return false if (!redirectAllowed(cb.redirect_uri)) return false return pkceOk(cb.code_verifier, cb.code_challenge, cb.code_challenge_method) } 落地建议强制 `S256`,拒绝 `plain`,并校验 `code_verifier` 长度与字符集。`redirect_uri` 实施精确白名单匹配,开发例外仅限本地 `localhost`。`state/nonce` 与会话绑定,回调时逐项对齐校验并记录审计。结合授权服务器的 `code` 一次性使用与短期有效策略,减少拦截面。验证清单`code_verifier` 是否满足长度与字符集约束。`code_challenge_method` 是否为 `S256` 且校验通过。`redirect_uri` 是否命中白名单且协议安全。`state/nonce` 是否与会话记录一致。

发表评论 取消回复