跳转到主要内容

OAuth PKCE

本文约需 2 分钟阅读

PKCE (Proof Key for Code Exchange,发音为“pixy”) 是为 OAuth 2.0 的授权码流程添加的安全扩展。它于 2015 年作为 RFC 7636 标准化。最初是为移动应用等无法安全保存 client_secret 的公共客户端而设计的,但由于其有效性,OAuth 2.1 正朝着对所有客户端类型强制使用的方向发展。

什么是授权码拦截攻击

要理解 PKCE 所解决的威胁,首先需要了解攻击的原理。在 OAuth 2.0 的授权码流程中,用户在授权服务器登录后,授权码会通过重定向 URI 返回给客户端。在移动操作系统中,重定向使用自定义 URL 方案 (例如 myapp://),但如果恶意应用注册了相同的 URL 方案,就能够拦截授权码。

正规应用发送授权请求
用户登录并同意
恶意应用拦截授权码
用拦截的授权码获取令牌

SPA (单页应用) 也存在类似的风险,因为授权码可能通过浏览器历史记录、引用来源标头或浏览器扩展程序泄露。

code_verifier 与 code_challenge 的原理

PKCE 以密码学方式保证“只有发送授权请求的本人才能获取令牌”。其原理很简单,使用两个值。

要素生成时机作用
code_verifier在授权请求前由客户端生成43-128 个字符的随机字符串。仅由客户端持有的秘密
code_challenge由 code_verifier 计算得出SHA-256 哈希的 Base64URL 编码。包含在授权请求中
生成并保存 code_verifier
将 code_challenge 附加到授权请求
接收授权码
用 code_verifier + 授权码请求令牌
服务器验证 → 发行令牌

即使攻击者拦截了授权码,没有 code_verifier 也无法获取令牌。由于无法从 code_challenge 反推出 code_verifier (SHA-256 的单向性),因此即使截获授权请求也毫无意义。

在公共客户端中的必要性

在传统的 OAuth 2.0 中,机密客户端 (服务器端应用) 可以用 client_secret 保护令牌端点。然而 SPA 和移动应用的源代码掌握在用户手中,因此无法安全地嵌入 client_secret。PKCE 解决了这个问题,即使没有 client_secret 也能证明授权码的合法持有者。

PKCE 作为 CSRF 的防护措施同样有效。与 state 参数不同,PKCE 执行密码学验证,因此提供更稳固的保护。与会话令牌的管理相结合,可以提升整个认证流程的安全性。

在 OAuth 2.1 中的强制化

在 OAuth 2.1 (截至 2025 年仍处于草案阶段) 中,PKCE 对所有客户端类型都成为强制要求。即使是机密客户端也必须使用 PKCE。这基于“纵深防御 (多层防御)”的理念。即使 client_secret 泄露,只要有 PKCE 就能防止授权码被拦截。在使用 OpenID Connect 时,也强烈建议同时使用 PKCE。

常见的实现错误

  • 在 code_challenge_method 中使用 plain。由于 plain 会将 code_verifier 原样作为 code_challenge 发送,一旦被截获就毫无意义。务必使用 S256 (SHA-256)
  • 将 code_verifier 保存在本地存储 (local storage) 而非会话存储 (session storage) 中。这会增加被 XSS 攻击读取的风险
  • code_verifier 的熵不足。应使用密码学安全的随机数生成器 (Web Crypto API 的 getRandomValues)

一并阅读OAuth 权限的陷阱API 密钥管理的文章,就能看清整个授权流程的安全设计。在实务中,何时改用 API 密钥也是一个重要的判断要点。OAuth 安全相关书籍 (Amazon)也推荐用来进行系统性学习。

相关术语

这篇文章对您有帮助吗?

XHatena