花拾录
← 返回知识库

OAuth 2.0 授权码模式中的 PKCE:为什么公共客户端必须用它

信息安全AI2026/09/220 阅读0 评论

问题:公共客户端为什么不能安全保存 client_secret

在 OAuth 2.0 授权码模式(Authorization Code Grant)中,传统做法是用 client_secret 来证明「换 token 的请求来自合法客户端」。

但移动 App、桌面应用、单页应用(SPA)这类**公共客户端(public client)**无法安全保存 client_secret:

  • 移动 App 的代码可以被反编译,硬编码的密钥能被提取;
  • SPA 的 JavaScript 完全暴露在浏览器中;
  • 桌面应用的二进制文件同样可被逆向。

一旦 client_secret 泄露,攻击者就能伪装成该客户端,配合被窃取的授权码换取访问令牌。

授权码拦截攻击

授权码模式依赖「授权码只对合法客户端有效」这一假设。攻击者若能拦截授权码,就可能用它换取 token。常见场景包括:

  • 移动系统上恶意 App 注册相同的自定义 URL Scheme 来截获重定向;
  • 浏览器或系统环境中的其他应用监听回调;
  • 授权码出现在日志、Referer 等位置被泄露。

对公共客户端来说,光有授权码不足以证明「换 token 的请求来自当初发起授权的那个客户端」。

PKCE 的思路

PKCE(Proof Key for Code Exchange,发音近似「pixy」,RFC 7636)引入一个每次授权都重新生成的随机秘密,并把它和授权请求、token 请求绑定起来。

核心是两个值:

  • code_verifier:客户端生成的高熵随机字符串,全程留在客户端本地,不经过浏览器重定向;
  • code_challenge:由 code_verifier 计算得到,随授权请求一起发送。

RFC 7636 规定的转换方式有两种:

  • S256:code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
  • plain:code_challenge = code_verifier(不推荐,仅用于无法做 SHA-256 的极端受限环境)

关键点:攻击者即使截获了授权码,也拿不到 code_verifier,因此无法构造出合法的 token 请求。

流程步骤

  1. 客户端生成随机 code_verifier(RFC 7636 建议长度 43~128 个字符,使用 [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~" 字符集)。
  2. 计算 code_challenge = BASE64URL(SHA256(code_verifier)),并选定 code_challenge_method=S256。
  3. 发起授权请求时带上 code_challenge 和 code_challenge_method。
  4. 用户在授权服务器完成授权,重定向回客户端并携带 code。
  5. 客户端在本地取出 code_verifier,向 token 端点提交 code + code_verifier。
  6. 授权服务器用同样的算法校验 code_verifier 与之前收到的 code_challenge 是否匹配,匹配才签发 token。

为什么公共客户端必须用

  • 弥补没有 client_secret 的缺口:PKCE 让每次授权都有一次性秘密,攻击者无法复用被截获的授权码。
  • 防御授权码注入:攻击者用自己账号的授权码注入受害者客户端时,因为拿不到对应的 code_verifier,换 token 会失败。
  • 成本低、收益高:只需两端各加几个参数和一次哈希计算,无需改动整体架构。

实践建议

  • 公共客户端优先使用 S256,避免 plain。
  • code_verifier 必须使用密码学安全的随机数生成器,且每次授权都重新生成,不要复用。
  • 授权服务器对公共客户端应要求 PKCE;对机密客户端,当前最佳实践(OAuth 2.0 Security Best Current Practice)也建议使用 PKCE。
  • PKCE 不能替代 state 参数:state 用于防 CSRF 和关联请求,两者作用不同,建议同时使用。
  • 结合精确的重定向 URI 白名单,避免开放重定向带来的额外风险。

PKCE 不是可选项,而是公共客户端在授权码模式下的基础防线。

评论(0)

  • 还没有评论,来抢沙发~

相关文章