让 OAuth 授权码流程更安全的 PKCE 技术详解

发布于:2025-08-03 ⋅ 阅读:(11) ⋅ 点赞:(0)

目录

PKCE 要解决的核心问题是什么?

PKCE 的工作原理

为什么 PKCE 是有效的?

小结


PKCE,全称为 Proof Key for Code Exchange(发音类似 "pixy"),是 OAuth 2.0 的一个关键安全扩展,定义在 RFC 7636 中。PKCE 技术最初是为保护移动和单页应用(SPA)这类“公共客户端”而设计的,但由于其显著的安全优势,现已被视为对所有类型客户端都适用的最佳实践,并在 OAuth 2.1 中成为强制要求。

PKCE 要解决的核心问题是什么?

要理解 PKCE,首先要明白它要防范的攻击——授权码拦截攻击 (Authorization Code Interception Attack)

在标准的 OAuth 2.0 授权码流程中,存在一个短暂的窗口期,攻击者有可能截获到授权码(Authorization Code)。这种情况在移动设备上尤其常见,恶意应用可以注册一个自定义的 URL Scheme,从而拦截从浏览器返回给合法应用的授权码。

攻击流程如下:

  1. 恶意应用启动授权流程: 恶意应用使用合法应用的 client_id 在用户浏览器中发起授权请求。
  2. 用户授权: 用户在授权服务器上登录并同意授权,以为是在给合法应用授权。
  3. 授权码被拦截: 授权服务器将授权码通过重定向方式发送回合法应用。此时,恶意应用通过事先注册的 URL Scheme 拦截了这个重定向请求,从而窃取到了授权码。
  4. 恶意应用交换令牌: 恶意应用拿到授权码后,立即向令牌端点(Token Endpoint)发起请求,用授权码交换访问令牌(Access Token)。
  5. 攻击成功: 由于公共客户端(如手机App)没有客户端密钥(Client Secret)来验证自己,令牌端点仅凭授权码就颁发了访问令牌。恶意应用自此获得了访问用户受保护资源的权限。

问题的关键在于:仅凭授权码,不足以证明交换令牌的客户端就是最初发起请求的那个客户端。

PKCE 的工作原理

PKCE 技术的核心思想是一次性密码,即在授权流程中动态地创建一个只有合法客户端才知道的信息,并用它来证明自己的身份。PKCE 需要引入两个关键参数:

  • code_verifier: 一个高熵的、加密安全的随机字符串。可以理解为客户端创建的临时“密码”。这个值只存在于客户端,绝不能在网络中直接传输。
  • code_challenge: 由 code_verifier 经过特定算法转换而来的字符串。可以理解为对上述“密码”进行哈希或摘要后生成的密码提示。

PKCE 的详细工作流程如下:

第一步:客户端发起授权请求

  1. 生成 code_verifier: 在发起授权请求之前,客户端首先在本地生成一个随机的 code_verifier。例如:dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
  2. 生成 code_challenge: 客户端选择一个转换算法(code_challenge_method),通常是 S256 (即 SHA-256 哈希后进行 Base64url编码),然后根据 code_verifier 生成 code_challenge。假如 code_challenge = BASE64URL-ENCODE(SHA256(code_verifier)),例如:E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  3. 发送授权请求: 客户端将 code_challenge 和 code_challenge_method 作为参数,随授权请求一起发送给授权服务器的授权端点(Authorization Endpoint)。
GET /authorize?
response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=YOUR_REDIRECT_URI
&scope=read
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256

注意: 此时发送的是 code_challenge(密码提示),而不是 code_verifier(原始密码)。

第二步:授权服务器处理并返回授权码

  1. 存储 code_challenge: 授权服务器收到请求后,会存储 code_challenge 和 code_challenge_method,并将它们与将来要颁发的授权码进行关联。
  2. 用户授权: 用户登录并同意授权。
  3. 返回授权码: 授权服务器将授权码(code)通过重定向方式返回给客户端。

HTTP/1.1 302 Found
Location: YOUR_REDIRECT_URI?code=SplxlOBeZQQYbYS6WxSbIA

第三步:客户端使用授权码交换访问令牌

发起令牌交换请求: 客户端收到授权码后,向授权服务器的令牌端点(Token Endpoint)发起 POST 请求。在此次请求中,客户端必须包含上一步返回的授权码,并且必须附上在第一步中生成的原始 code_verifier。

POST /token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=YOUR_REDIRECT_URI
&client_id=YOUR_CLIENT_ID
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

注意: 这是 code_verifier(原始密码)唯一一次在网络中传输,并且是直接发送给令牌端点,不会经过浏览器重定向。

第四步:授权服务器验证并颁发令牌

  1. 验证 code_verifier: 令牌端点收到请求后,会根据之前存储的 code_challenge_method(例如 S256),对请求中收到的 code_verifier 执行相同的转换算法。
  2. 比较结果: 将转换后的结果与之前随授权码一起存储的 code_challenge 进行比较。VERIFY(code_verifier) == code_challenge ?
  3. 颁发令牌:如果两者匹配,说明这个请求确实是由最初发起授权流程的那个客户端发送的。授权服务器验证通过,正常颁发访问令牌和刷新令牌。如果不匹配,说明授权码可能被拦截了,因为攻击者无法提供正确的 code_verifier。授权服务器将拒绝请求,并且立即使被盗用的授权码失效。

为什么 PKCE 是有效的?

PKCE 的有效性在于将授权流程分成了两个部分,并为令牌交换环节增加了一个动态的、一次性的验证凭据。

  • 攻击者即使能拦截到授权码 (code),也无法获取到原始的 code_verifier。
  • code_verifier 不会像授权码那样经过浏览器重定向,而是通过客户端与令牌端点之间的直接、安全的后端信道传输。
  • 由于哈希算法(如 SHA-256)的单向性,攻击者无法通过 code_challenge 反推出 code_verifier。

因此,只有真正的客户端才能同时提供有效的授权码和与之匹配的 code_verifier,从而成功换取访问令牌。

小结

特性

描述

核心目的

防止授权码拦截攻击,确保只有合法的客户端才能用授权码交换令牌。

关键参数

code_verifier (客户端秘密) 和 code_challenge (公开的“挑战”)。

工作流程

客户端在授权请求中发送 code_challenge,在令牌请求中发送 code_verifier。

安全性

code_verifier 的秘密性 和 哈希算法的单向性保证了攻击者无法伪造请求。

当前地位

OAuth 2.1 强制要求,被视为所有 OAuth 2.0 实现的最佳安全实践。

通过强制要求使用 PKCE,OAuth 2.1 大大提升了安全性,简化了开发者的选择,能够更轻松地构建起稳固、安全的授权系统。