PKCE 所挫败的攻击
在 2.0 授权码流中,授权服务器把一个短命的授权码交给客户端,客户端随后用它换取令牌。机密客户端用一个客户端秘密来保护这次交换。但公共客户端,单页应用与移动应用,无法保守一个秘密:任何发送到浏览器或设备上的东西都可被提取。这便留下一个漏洞。若攻击者拦截了授权码(通过一个为同一重定向注册的恶意应用、一份泄露的日志或一个精心构造的 URL),他便能自行将其兑换为令牌。
,即 Proof Key for Code Exchange(RFC 7636,读作 “pixy”),无需一个预共享的秘密便堵上了那个漏洞。它把授权码绑定到一个唯有真正的客户端知晓的一次性秘密。
验证者与挑战如何契合
该流程增加了两个值:
- code_verifier 是客户端生成并保留的一个高熵随机字符串。RFC 7636 要求来自非保留集(字母、数字以及
- . _ ~)的 43 到 128 个字符。 - code_challenge 由验证者派生而来。对 S256 方法,它是
BASE64URL(SHA256(ASCII(code_verifier)))。
序列是:
- 客户端生成一个新的
code_verifier、派生出code_challenge,并在授权请求中只发送挑战。 - 授权服务器存储该挑战,并如常返回一个授权码。
- 在令牌请求时,客户端发送验证者。
- 服务器以同样的方式对验证者做哈希,并将其与所存储的挑战核对。不匹配,则无令牌。
安全性系于一个事实:挑战是验证者的一个单向哈希。一个拦截了授权码的攻击者仍无法完成交换,因为他从未见过验证者,也无法从挑战反推出它。
为何用 S256,而非 plain
RFC 7636 定义了两种方法。plain 方法把挑战设为等于验证者,若授权请求本身被观察到,这便毫无保护。S256 方法只发送 哈希,于是验证者一直隐藏到最终那次受 TLS 保护的令牌调用。只要客户端能计算 SHA-256,便要求使用 S256,而那在一切要紧之处皆然;plain 仅为那种无法做哈希的罕见受限设备而存在。
从可选到强制
PKCE 起初是面向公共客户端的一个扩展,但指导意见已果断转向。OAuth 2.0 Security Best Current Practice(RFC 9700)现在要求所有授权码客户端使用 PKCE,机密客户端也包括在内,因为它还能防御某些代码注入攻击。若你今天正在构建或测试一个 OAuth 或 OpenID Connect 集成,请把 PKCE 当作默认,而非附加项。
PKCE 工具 生成一个合规的验证者、用 Web Crypto 派生其 S256 挑战,并将任意验证者与 RFC 的长度与字符集规则相核对,全部在本地。验证者从不离开你的浏览器。