2012年7月,OAuth 2.0规范的主要作者Eran Hammer宣布辞职。他在博客中写道:“OAuth 2.0是一个坏协议。“这不是一时冲动——作为OAuth 1.0的创建者和OAuth 2.0的首席编辑,他在三年间目睹了协议在商业利益博弈中逐渐失去安全初心。十多年后,OAuth已成为互联网授权的事实标准,每月处理着数以十亿计的登录请求。但Hammer的警告正在被验证:OAuth本身是安全的,但实现OAuth的人总是犯错。

2023年,安全研究人员发现Grammarly、Vidio、Bukalapak三大平台存在严重的OAuth漏洞,影响数亿用户。攻击者无需任何用户交互就能劫持账户。问题不在协议本身——而在开发者对协议的理解偏差。

协议的妥协:从安全到便利

OAuth 2.0诞生于一个微妙的历史时刻。2010年,OAuth 1.0因为签名机制的复杂性被开发者诟病。每请求都需要计算HMAC签名,参数排序规则繁琐,实现成本高昂。科技公司们需要一个更简单的协议。

OAuth 2.0的设计哲学发生了根本转变:放弃强制性的密码学签名,依赖TLS保护通信安全,将大量安全决策交给实现者自行处理。这个决定降低了入门门槛,却也埋下了隐患——规范变得模糊,可选配置太多,安全边界不清晰。

RFC 6749定义了四种授权类型:授权码模式、隐式模式、密码模式和客户端凭证模式。其中隐式模式(Implicit Grant)专为无法安全存储密钥的浏览器应用设计,直接在URL片段中返回access token。这个看似便捷的设计,后来被证明是最危险的选择。

最危险的简化:隐式模式的消亡

隐式模式的问题从URL片段开始。当授权服务器将access token放在https://client-app.com/callback#access_token=xxx中返回时,这个token会出现在多个危险位置:

浏览器历史记录会完整保存这个URL。用户的书签可能意外保存。更致命的是Referer头部——如果回调页面加载了任何外部资源,token会随Referer头发送到第三方服务器。

2019年,OAuth工作组正式建议废弃隐式模式。RFC 9700(2025年1月发布)明确指出:隐式模式和其他在授权响应中直接返回access token的响应类型都应避免使用。

替代方案是授权码模式配合PKCE(Proof Key for Code Exchange)。PKCE最初是为了保护移动应用而设计,现在已成为所有客户端的推荐配置。

PKCE:亡羊补牢的安全扩展

PKCE的核心思想很巧妙:客户端在发起授权请求前,生成一个随机的code_verifier,计算其SHA-256哈希值作为code_challenge发送给授权服务器。授权服务器将challenge与授权码绑定。当客户端用授权码换取token时,必须提供原始的code_verifier,服务器验证其哈希值是否匹配。

sequenceDiagram
    participant User as 用户
    participant Client as 客户端
    participant AuthServer as 授权服务器
    
    Client->>Client: 生成code_verifier (随机字符串)
    Client->>Client: 计算code_challenge = SHA256(verifier)
    Client->>AuthServer: 授权请求 (code_challenge)
    AuthServer->>User: 显示授权页面
    User->>AuthServer: 同意授权
    AuthServer->>Client: 返回授权码 (code)
    Client->>AuthServer: Token请求 (code + code_verifier)
    AuthServer->>AuthServer: 验证SHA256(verifier) == challenge
    AuthServer->>Client: 返回access_token

这个机制阻止了授权码窃取攻击。即使攻击者截获了授权码,没有code_verifier也无法换取token。RFC 9700要求:公共客户端必须使用PKCE,机密客户端也强烈推荐使用。

CSRF攻击:缺失的state参数

OAuth规范明确建议使用state参数防止跨站请求伪造攻击。然而,大量实现中这个参数要么缺失,要么使用可预测值。

攻击场景很典型:攻击者先在自己的账户完成OAuth授权流程,获取一个有效的授权码。然后诱导受害者在已登录状态下访问恶意链接,这个链接会触发客户端应用用攻击者的授权码完成登录。如果客户端没有验证state参数,就会将受害者的账户绑定到攻击者的身份上。

2025年12月,HedgeDoc披露了一个真实漏洞:其OAuth实现缺少state参数验证,攻击者可以注入自己的授权码,接管用户账户。修复仅需要几行代码——在授权请求时生成随机state,在回调时严格验证。

redirect_uri:安全链的最薄弱环节

redirect_uri参数本应是授权服务器验证回调地址的安全机制。实现中的疏漏让这个机制形同虚设。

最常见的问题是宽松匹配。授权服务器只检查redirect_uri是否以注册的前缀开头,而不是精确匹配。攻击者可以构造https://client-app.com.callback.attacker.comhttps://[email protected]这样的地址,绕过验证。

更隐蔽的是通过开放重定向器(Open Redirector)窃取token。假设客户端应用存在一个开放重定向漏洞https://client-app.com/redirect?url=xxx,且这个路径被注册为有效的redirect_uri。攻击者可以将redirect_uri设为https://client-app.com/redirect?url=https://attacker.com,授权码会先到达客户端的重定向器,然后被转发到攻击者服务器。

2023年,研究人员在Semrush发现了另一种绕过方式:IDN同形异义字攻击。攻击者使用Unicode字符注册视觉上相似的域名,redirect_uri验证被绑过。

授权码注入:一个代码换身份

授权码注入是最隐蔽的攻击方式之一。攻击者构造一个恶意网站,诱导用户完成OAuth授权。用户以为自己在为恶意网站授权,实际上生成的授权码会被攻击者用于受害者在目标网站的账户。

2023年Salt Security披露的案例堪称教科书级别。攻击者创建了一个看似正常的网站YourTimePlanner.com,吸引用户使用Facebook登录。当用户Dan授权后,攻击者获得了Facebook为YourTimePlanner生成的access token。

问题出在Vidio、Bukalapak、Grammarly这些网站:它们接收OAuth token后,没有验证token是否是为自己应用生成的。攻击者用从YourTimePlanner获取的Dan的token,直接调用Vidio的OAuth回调接口,系统就直接让攻击者以Dan的身份登录了。

Facebook文档明确要求:开发者必须在调用用户信息API前,先调用/debug_token接口验证token的app_id是否匹配自己的应用。但这些平台都省略了这一步。

mix-up攻击:混乱的授权服务器

当客户端应用支持多个OAuth提供商时,可能遭遇mix-up攻击。攻击者注册一个恶意OAuth提供商,诱导客户端应用将其配置为合法提供商。

攻击流程:受害者点击"使用Google登录”,攻击者拦截请求并修改授权端点指向恶意服务器。恶意服务器重定向到真实的Google授权页面,用户看到熟悉的Google登录界面,完成授权。但返回的token被攻击者截获。

RFC 9207引入了iss参数作为对策:授权服务器在授权响应中返回自己的标识符,客户端验证这个标识符是否与预期一致。如果OAuth提供商支持Authorization Server Metadata(RFC 8414),客户端可以通过/.well-known/oauth-authorization-server获取配置信息,自动检测安全特性支持情况。

Token存储:localStorage的诱惑与陷阱

前端开发者喜欢用localStorage存储token——简单、直观、无需处理cookie。这是危险的选择。

localStorage对同源下的所有JavaScript代码完全开放。任何XSS漏洞都能轻易窃取token。考虑到前端生态的复杂性——第三方库、广告脚本、分析代码——XSS攻击面相当大。

更安全的做法是使用HttpOnly cookie存储refresh token,access token保存在内存中。配合cookie的SameSite属性和CSRF token,形成纵深防御。

但这并非万灵药。cookie同样面临风险:子域名攻击、CORS配置错误、CRLF注入。2024年的一项研究表明,超过60% of OAuth实现存在token存储安全问题。

客户端认证:从共享密钥到非对称密钥

传统OAuth实现中,机密客户端使用client_secret进行认证。问题在于:密钥需要存储在授权服务器数据库中,一旦数据库泄露,所有客户端密钥都暴露。

RFC 9700推荐使用非对称密钥进行客户端认证:客户端使用私钥签名JWT断言(private_key_jwt方法),授权服务器用公钥验证。这种方式下,授权服务器不需要存储敏感的对称密钥,即使服务器被攻破,攻击者也无法伪造客户端认证。

另一种选择是mTLS客户端认证(RFC 8705):客户端和授权服务器建立双向TLS连接,客户端证书绑定到特定的client_id。这种方式更底层、更安全,但部署复杂度也更高。

DPoP:Token绑定的新方向

Bearer token的特性是"持有即拥有”——任何人拿到token都能使用。RFC 9449定义的DPoP(Demonstrating Proof of Possession)改变了这个模型:token绑定到特定的加密密钥,每次使用token都需要提供拥有密钥的证明。

DPoP的工作原理:客户端生成一对公私钥。请求token时,用私钥签名一个JWT证明,授权服务器返回绑定到公钥的access token。后续每次API调用,客户端都要在DPoP头部提供新的签名证明。即使token被盗,攻击者没有私钥也无法使用。

这是OAuth安全演进的重要方向。RFC 9700明确建议:授权服务器和资源服务器应该实现sender-constraining机制,如mTLS或DPoP。

Refresh Token Rotation:被盗后的止损

Refresh token是长期凭证,一旦泄露后果严重。Refresh Token Rotation机制提供了一种检测和止损方案:每次使用refresh token换取新access token时,授权服务器同时返回一个新的refresh token,旧的立即失效。

如果检测到已失效的refresh token被再次使用,说明可能存在token泄露,授权服务器可以撤销整个token家族。

Lucid公司在2023年的实践中发现了一个有趣的边界情况:当用户在多个设备同时刷新token时,可能导致误报。他们开发了一套启发式算法,区分正常并发使用和可疑的token复用。

2025年的OAuth安全基线

RFC 9700于2025年1月正式发布,总结了OAuth 2.0的最佳安全实践。核心要求包括:

废弃的危险模式:

  • 隐式授权类型(response_type=token)
  • 资源所有者密码凭证授权
  • 不精确的redirect_uri匹配

强制要求:

  • 所有公共客户端必须使用PKCE
  • 精确匹配redirect_uri
  • 使用state或nonce防止CSRF
  • 授权响应必须通过加密连接传输

强烈推荐:

  • 机密客户端使用非对称密钥认证
  • 实现sender-constraining(mTLS或DPoP)
  • 限制access token的audience和scope
  • 发布Authorization Server Metadata

OAuth 2.1正在制定中,将把这些最佳实践整合到核心规范中。

实施检查清单

对于正在实现OAuth的开发者,以下检查清单可以避免大多数常见漏洞:

授权请求阶段:

  • 生成加密安全的随机state参数
  • 对于公共客户端,生成PKCE的code_verifier和code_challenge
  • 使用S256方法计算code_challenge
  • 明确指定最小必要的scope

回调处理阶段:

  • 严格验证state参数与会话绑定
  • 验证授权码只能使用一次
  • 确保token交换通过安全的后端通道
  • 验证token的audience和issuer

Token管理:

  • Access token使用短过期时间(分钟级)
  • 实现refresh token rotation
  • 使用HttpOnly cookie存储refresh token
  • 实现token撤销机制

安全增强:

  • 配置正确的CORS策略
  • 实现适当的CSP策略
  • 记录异常授权行为
  • 定期审计第三方库依赖

OAuth 2.0不是一个可以直接"复制粘贴"的协议。每个实现决策都关乎安全边界。理解协议背后的威胁模型,才能在便利与安全之间找到正确的平衡点。


参考资料

  1. RFC 6749: The OAuth 2.0 Authorization Framework, IETF, October 2012
  2. RFC 6819: OAuth 2.0 Threat Model and Security Considerations, IETF, January 2013
  3. RFC 7636: Proof Key for Code Exchange by OAuth Public Clients, IETF, September 2015
  4. RFC 9700: Best Current Practice for OAuth 2.0 Security, IETF, January 2025
  5. RFC 9449: OAuth 2.0 Demonstrating Proof of Possession (DPoP), IETF, September 2023
  6. Eran Hammer, “OAuth 2.0 and the Road to Hell”, July 2012
  7. Salt Security, “Oh-Auth — Abusing OAuth to take over millions of accounts”, October 2023
  8. PortSwigger, “OAuth 2.0 authentication vulnerabilities”, Web Security Academy
  9. Auth0 Documentation, “Authorization Code Flow with PKCE”
  10. WorkOS, “OAuth common attacks and how to prevent them”, March 2025