一句话定义
OAuth PKCE(Proof Key for Code Exchange,读作 "pixy",直译"用于换取授权码的证明密钥")是 OAuth 2.0 授权码流程上的一层加固:客户端在发起登录时先提交一个由随机数算出的"挑战值",换令牌时再交出原始随机数,让偷到授权码的人换不出令牌。
它要补的漏洞
OAuth 2.0 授权码流程(Authorization Code Flow)大致是:用户被跳转到授权服务器登录并同意,服务器把浏览器重定向回客户端,带上一串 authorization code;客户端再拿这串码去换 access token(真正能调接口的凭证)。
问题出在"客户端是谁"这件事上。后端 Web 应用有服务器,可以把 client_secret 藏得很好;但手机 App、桌面软件、浏览器里的单页应用(SPA)属于公开客户端(public client),代码和配置就在用户设备上,secret 能被反编译或抓包扒出来——等于把钥匙和锁一起给了用户。
更糟的是,就算不要 secret,授权码本身也可能被截胡。手机上一个自定义回调地址(比如 myapp://callback)常常能被多个 App 注册,恶意 App 可以抢先接住重定向,拿到那串 code,再冒充你的客户端去换 token。
打个比方
像商场存包处:你存包拿到一张号码牌(code),取包时递上号码牌就行。号码牌被别人看到,包就归别人了。PKCE 的做法是——存包时你先想一句只有你知道的口令,只把口令的"指纹"告诉柜台;取包时除了号码牌,还得报出原句口令,柜台现算一遍指纹核对。
三步走
1. 客户端每次授权前生成一个随机字符串 code_verifier。
2. 算它的哈希:code_challenge = BASE64URL(SHA256(code_verifier)),随授权请求一起发给服务器(标记方法为 S256)。
3. 拿 code 换 token 时,把原始 code_verifier 一并提交。服务器重算哈希,对不上就拒绝。
关键在于:verifier 从头到尾没经过浏览器重定向,只待在发起登录的那个进程内存里,用完即扔。
它防住了谁
- 同设备上抢到回调、截获 code 的恶意 App——没有 verifier,哈希对不上。
- 从重定向 URL 里偷看 code 的中间人(浏览器其他页面、日志、Referer 泄露)。
- 把别人的 code 硬塞给你客户端的注入类攻击——同样卡在 verifier 上。
| 对比项 | client_secret | PKCE 的 code_verifier |
|---|---|---|
| 存放位置 | 客户端配置里,长期不变 | 每次登录临时生成,仅在内存 |
| 公开客户端能否保密 | 不能,可被逆向提取 | 可以,从不下发、不落地 |
| 防授权码拦截 | 基本无能为力 | 有效 |
| 谁该用 | 有后端可保密的机密客户端 | 所有客户端;公开客户端必须 |
对从业者的意义
接第三方登录(GitHub、Google 等),或给 AI 应用做 MCP(Model Context Protocol)远程服务器的授权时,SDK 里那个 code_verifier / code_challenge 参数就是它,默认打开即可,别手写成固定值。OAuth 2.1 的草案已把 PKCE 从"公开客户端必需"扩大为对所有客户端的要求,具体条款以官方规范页面为准。它不替代 HTTPS,也不替代 token 的过期与撤销策略——只是一块很关键的拼图。
