Back

Web 应用中 OAuth 安全的最佳实践

Web 应用中 OAuth 安全的最佳实践

OAuth 2.0 已成为 API 身份验证的标准,但安全地实现它仍然具有挑战性。随着令牌盗窃、已弃用的流程以及浏览器特定漏洞每天都在威胁着 Web 应用程序,开发人员需要明确的指导来了解什么方法真正有效。本文涵盖了 2025 年您需要的基本 OAuth 2.0 安全最佳实践,从 RFC 9700 要求到 SPA 特定模式。

核心要点

  • 永远不要在 localStorage 或 sessionStorage 中存储 OAuth 令牌——对于 SPA 使用内存存储
  • 在 OAuth 2.1 下,所有客户端都必须使用带 PKCE 的授权码流程
  • 实施刷新令牌轮换和短期访问令牌(15-30 分钟)
  • 考虑使用前端后端(BFF)模式,将令牌完全排除在浏览器之外

为什么 OAuth 安全比以往更重要

OAuth 通过消除应用程序之间的密码共享来保护用户数据。OAuth 不是将您的凭据交给第三方应用(就像交出您的房门钥匙),而是颁发临时的、有作用域限制的令牌——更像是给代客泊车员一把只能启动汽车的钥匙。

但问题在于:令牌是有价值的攻击目标。被盗的访问令牌可以立即授予 API 访问权限。糟糕的实现选择——在 localStorage 中存储令牌、使用已弃用的流程或跳过 PKCE——会将小漏洞转变为重大安全漏洞。

核心安全风险及应对方法

令牌盗窃和存储

不要这样做: 在 localStorage 或 sessionStorage 中存储令牌。任何 XSS 攻击都可以读取这些值并将它们泄露到攻击者控制的服务器。

应该这样做: 对于 SPA,仅将令牌保存在内存中。对于服务器端应用,将它们存储在加密的服务器端会话中。使用短期访问令牌(15-30 分钟)来限制任何盗窃造成的损害。

应避免的已弃用流程

隐式流程已经过时。RFC 9700 和 OAuth 2.1 都禁止使用它。为什么?因为它直接在 URL 片段中发送令牌,将它们暴露给浏览器历史记录、引用头以及页面上的任何 JavaScript。

资源所有者密码凭据流程也应该避免——它通过要求应用直接处理用户密码来违背 OAuth 的整个目的。

始终使用: 对所有客户端(包括 SPA 和移动应用)使用带 PKCE 的授权码流程。

现代标准:RFC 9700 和 OAuth 2.1

PKCE 现在是强制性的

PKCE(代码交换证明密钥)可防止授权码拦截攻击。其工作原理如下:

// Generate code verifier and challenge
const verifier = generateRandomString(128);
const challenge = base64url(sha256(verifier));

// Include challenge in authorization request
const authUrl = `https://auth.example.com/authorize?` +
  `client_id=app123&` +
  `code_challenge=${challenge}&` +
  `code_challenge_method=S256`;

// Send verifier when exchanging code for token
const tokenResponse = await fetch('/token', {
  method: 'POST',
  body: JSON.stringify({
    code: authorizationCode,
    code_verifier: verifier
  })
});

OAuth 2.1 使 PKCE 对所有授权码流程都是强制性的,而不仅仅是公共客户端。

使用 mTLS 或 DPoP 进行令牌绑定

令牌绑定确保被盗令牌无法被攻击者使用。两种主要方法:

  • mTLS(双向 TLS): 将令牌绑定到客户端证书
  • DPoP(持有证明演示): 使用加密证明而无需证书

两者都通过将令牌加密绑定到合法客户端来防止令牌重放攻击。

刷新令牌轮换

永远不要重复使用刷新令牌。每次刷新都应颁发新的刷新令牌并使旧令牌失效。这限制了被盗刷新令牌滥用的时间窗口:

// Server-side refresh token handling
async function refreshAccessToken(oldRefreshToken) {
  // Validate and revoke old refresh token
  await revokeToken(oldRefreshToken);
  
  // Issue new token pair
  return {
    access_token: generateAccessToken(),
    refresh_token: generateRefreshToken(), // New refresh token
    expires_in: 1800
  };
}

SPA OAuth 安全:特殊考虑

单页应用面临独特的挑战,因为所有代码都在浏览器中运行。浏览器是敌对环境——假设那里的任何数据都可能被泄露。

前端后端(BFF)模式

最安全的方法是将令牌完全排除在浏览器之外。轻量级后端代理处理 OAuth 流程并在服务器端维护令牌,使用安全的、httpOnly、sameSite cookie 来管理 SPA 会话。

令牌处理器模式

对于希望在没有后端复杂性的情况下获得 SPA 优势的团队,令牌处理器模式提供了一个折中方案。它使用专门的代理:

  • 处理 OAuth 流程
  • 安全存储令牌
  • 向 SPA 颁发短期会话 cookie
  • 将 cookie 认证请求转换为令牌认证的 API 调用

如果必须在浏览器中存储令牌

当 BFF 不可行时:

  • 仅在内存中存储令牌,永远不要使用 localStorage
  • 使用 service workers 隔离令牌访问
  • 实施激进的令牌过期时间(5-15 分钟)
  • 永远不要在浏览器中存储刷新令牌

实施检查清单

✓ 对所有客户端使用带 PKCE 的授权码流程
✓ 实施精确的重定向 URI 匹配
✓ 将令牌生命周期设置为最小可行持续时间
✓ 为公共客户端启用刷新令牌轮换
✓ 使用 state 参数进行 CSRF 保护
✓ 为高安全性应用实施令牌绑定(mTLS/DPoP)
✓ 对于 SPA:考虑 BFF 或令牌处理器模式
✓ 监控 OAuth 2.1 合规性要求

结论

在 Web 应用中保护 OAuth 需要理解协议的优势和面向您架构的特定威胁。从 RFC 9700 的要求开始:强制性 PKCE、禁止隐式流程以及正确的令牌处理。对于 SPA,认真考虑通过 BFF 或令牌处理器模式将令牌完全排除在浏览器之外。这些不仅仅是最佳实践——它们是 2025 年 OAuth 安全的最低标准。

常见问题

不可以。即使是没有敏感数据的应用也不应该使用 localStorage 存储令牌。被盗令牌可用于账户接管、API 滥用,并作为更严重攻击的跳板。始终将令牌存储在内存中或使用服务器端会话。

是的。OAuth 2.1 要求所有授权码流程都使用 PKCE,包括机密客户端。PKCE 增加了针对授权码拦截攻击的纵深防御,这是单独使用客户端密钥无法防止的,特别是在涉及受损网络或恶意浏览器扩展的场景中。

使用前端后端模式,由后端管理刷新令牌,并通过安全 cookie 向 SPA 提供短期访问令牌。或者,当访问令牌过期时,使用带 prompt=none 的静默认证来获取新令牌,无需用户交互。

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay