Web 认证和授权协议
RFC 6749 OAuth 2.0
OAuth 2.0 总体框架
OAuth 2.0 是一个“授权协议”,用于让客户端在不掌握用户凭证的前提下,受控地访问资源服务器上的资源。
1 | +--------+ +---------------+ |
整个过程中的参与方如下:
| 参与方 | 标准术语 |
|---|---|
| 资源所有者(用户) | Resource Owner |
| 应用(客户端) | Client |
| Web 托管的应用资源(多为 JS 文件) | Web-hosted Client Resource |
| 用户代理(多为浏览器) | User Agent |
| 授权服务器 | Authorization Server |
| 资源服务器 | Resource Server |
整个过程中交互的资源如下:
| 资源 | 标准术语 |
|---|---|
| 授权码 | authorization code |
| 访问令牌 | access token |
| 刷新令牌 | refresh token |
| 重定向 URI | redirection URI |
其中 Client 可以分为两种:
| 类型 | 特征 | 示例 |
|---|---|---|
| Confidential Client | 能安全存密码 | 传统 Web Server |
| Public Client | 不能安全存密码 | SPA / Mobile / Desktop |
OAuth 2.0 授权码模式
授权码模式(Authorization Code Grant)是 OAuth 2.0 功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与服务提供商的认证服务器进行互动。工作流如下:
- User Agent 访问 Client 时,后者向 Authorization Server 发送请求并将前者导向 Authorization Server。
- User Agent 让 Resource Owner 登录 Authorization Server 并决策是否给 Client 授权。
- 若 Resource Owner 同意授权, Authorization Server 会返回一个 authorization code,并将 User-Agent 导向 Client 事先指定的 redirection URI。
- Client 收到 authorization code 后,在后台 向 Authorization Server 申请 access token。
- Authorization Server 核对 authorization code 和 redirection URI 并向 Client 发送 access token。
1 | +----------+ |
第一步中,Client 向 Authorization Server 发送的参数如下:
| 字段 | 是否可选 | 含义 |
|---|---|---|
| response_type | 必选 | 指定 OAuth 授权类型,本场景置为定值 code |
| client_id | 必选 | 标识 Client(预先在 Authorization Server 注册) |
| redirect_uri | 可选 | Client 的重定向地址,用于防止 code/token 泄露 |
| scope | 可选 | Client 请求的权限,做到最小权限原则 |
| state | 可选 | Client 指定的状态码,第三步里会被原样传回 |
1 | GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz |
第二步中,OAuth 不关心登录的具体细节。
第三步中,Authorization Server 返回给 User Agent(并最终触发对 Client 的调用)的结构体如下:
| 字段 | 是否可选 | 含义 |
|---|---|---|
| code | 必选 | 一次性授权码 |
| state | 条件必选 | 若 Client 传了 state 字段则原样返回 |
1 | 302 Found |
第四步中,Client 向 Authorization Server 发送申请的参数如下。如果是 Public Client,client_id 放于参数;如果是 Confidential Client,使用 HTTP Basic 凭据进行 client_id + client_secret 的认证。
| 字段 | 是否必选 | 说明 |
|---|---|---|
| grant_type | 必选 | OAuth 模式声明,本场景置为 authorization_code |
| code | 必选 | 一次性授权码 |
| redirect_uri | 条件必选 | 重定向 URI,若 Client 最初传了就必须原样传 |
| client_id | 可选 | Client 标识 |
1 | POST /token |
第五步中,Authorization Server 给 Client 回复的结构体如下:
| 字段 | 是否必选 | 说明 |
|---|---|---|
| access_token | 必选 | 访问令牌 |
| token_type | 必选 | 令牌类型,如 bearer 或 mac |
| expires_in | 可选 | 过期时间,单位为秒 |
| refresh_token | 可选 | 刷新令牌,用来获取下一次的访问令牌 |
| scope | 条件必选 | 权限范围,若与 Client 申请的范围一致可省略 |
1 | 200 OK |
token_type 的种类和用法交由对应的 RFC 来解释。RFC 6749 里举了两个例子:
- RFC 6750 Bearer Token:谁持有 token 谁就能使用(需配合 TLS)。这是 OAuth 协议的主流场景。
- OAuth-HTTP-MAC Message Authentication Code:客户端和授权服务器共享一个对称密钥。客户端不直接发送 access token,而是构造规范化请求字符串并用 MAC 计算出 HMAC,服务端做同样的运算来校验。尽管 MAC 的安全性更高,这个草案最终因为协议复杂性和密钥管理问题被淘汰。
OAuth 2.0 其他授权模式
除授权码模式外,RFC 6749 还定义了其他三种授权模式:
- 简化模式(Implicit Grant):主要为 Public Client 设计,跳过了"授权码"步骤,无需经过 Client 的后端服务器,全程在 User Agent 上操作。Authorization Server 返回的重定向 URI 里直接带有访问令牌,其被放置在 Path 的 Hash 部分(以
#分隔)。接下来 User Agent 回调 Web-hosted Client Resource 获取某个静态脚本来解析 URI Fragment 里的 access token,这个令牌最终交给 Client 使用。 - 密码模式(Resource Owner Password Credentials Grant):Resource Owner 直接向 Client 提供自己的用户名和密码。Client 使用这些信息向 Authorization Server 索要授权。通常用在 Resource Owner 对 Client 高度信任的场景,比如 Client 是操作系统的一部分,或者由一个著名公司出品。
- 客户端凭证模式(Client Credentials Grant):Resource Owner 向 Client 注册,Client 以自己的名义要求 Authorization Server 提供服务(过程中其实不存在授权的说法)。
随着安全实践演进,部分模式已被废弃或不推荐使用。下表整理了各模式的适用场景与安全评价:
| 模式名称 | 典型使用场景 | 是否推荐 | 安全评价 |
|---|---|---|---|
| 授权码模式 | Web 应用、Native App | ✅ 推荐 | 常配合 PKCE 和 state 使用 |
| 简化模式 | 纯前端 SPA | ❌ 已废弃 | 令牌易被截取,无法判断 Client 是否恶意 |
| 密码模式 | 高度可信的 Client | ❌ 已废弃 | 密码泄露,无法实现最小权限 |
| 客户端凭证模式 | 服务间通信 | ✅特定场景 | 只用于无用户上下文的机机授权 |
OAuth 2.0 令牌刷新机制
RFC 6749 中的 refresh token 用于安全地获取新的 access token,避免 Resource Owner 频繁重新登录。这个字段是可选的,由 Authorization Server 在颁发时决定(通常对长期访问场景才颁发)。工作流如下:
1 | +--------+ +---------------+ |
倒数第二步中,Client 向 Authorization Server 发送申请的参数如下:
| 字段 | 是否必选 | 说明 |
|---|---|---|
| grant_type | 必选 | OAuth 模式声明,本场景置为 refresh_token |
| refresh_token | 必选 | 表示早前收到的更新令牌 |
| scope | 条件必选 | 权限范围,不可超出上一次申请的范围,若一致可忽略 |
1 | POST /token |
第二步或最后一步中,Authorization Server 给 Client 返回的结构体和先前保持一致。
OAuth 最佳实践
Login CSRF 攻击和 state 校验
跨站伪造请求攻击(Cross-Site Request Forgery Attack):攻击者诱导已登录用户的浏览器,在用户不知情的情况下,向目标网站发起一个“伪造但合法”的请求(如转账、改密码、发邮件)。
标准 CSRF 的触发条件:用户已通过 Cookie 认证登录了目标网站。
登录跨站伪造请求攻击(Login CSRF Attack):攻击者在用户不知情的情况下让其登录到攻击者控制的账户。
Login CSRF 在 OAuth2.0 场景的触发条件:OAuth 流程里省略 state 参数。
Login CSRF 攻击流程:攻击者可以自己先走一遍 OAuth 前三步流程拿到一个有效的授权码,再构造 OAuth 第四步里原本由用户 Client 构造的请求,诱导用户的 User Agent 将其发送给 Authorization Server。
Login CSRF 防护方式:OAuth 流程里强制传入 state 参数,Client 收到回调地址后校验这个会话的 state。
授权码拦截攻击和 PKCE
授权码拦截攻击(Authorization Code Interception Attack):攻击者在用户设备上通过恶意应用或系统漏洞,窃听或劫持 OAuth 2.0 授权流程中的回调 URI 从而截获授权码,并在合法客户端完成令牌交换前抢先使用该授权码向授权服务器兑换访问令牌,从而冒充用户访问受保护资源。
授权码拦截攻击在 OAuth2.0 场景的触发条件:Public Client(无法用 client_secret)且授权码可劫持。
RFC 7636 Proof Key for Code Exchange by OAuth Public Clients:提出了授权码交换证明密钥(Proof Key for Code Exchange),使得即使 authorization code 被截获,攻击者也无法用它换出 access token,因为他们不知道一个只有合法客户端才知道的“动态密钥”。PKCE 的具体流程如下:
- Client 为本次会话生成
code_verifier和code_challenge两个字段,前者是 43-128 字节的高熵随机字符串,后者是前者的哈希,例如code_challenge=BASE64URL-ENCODE(SHA256(code_verifier))。 - 第一步 Client 向 Authorization Server 发起授权请求时,额外带上
code_challenge字段。 - 第四步 Client 向 Authorization Server 发起授权码换令牌时,必须提供原始的
code_code_verifier字段。
目前 RFC 7636 已被广泛采用,Google、Apple、Microsoft 等平台要求 Native App 必须使用 PKCE,而 Auth0、Okta 等平台甚至强制所有 Client(无论是否是 Public Client)使用 PKCE。
改进 Bearer Token
为了解决 Bearer Token 的安全缺陷,OAuth 社区提出的两种持有证明(Proof of Possession)模型。
RFC 8471 The Token Binding Protocol Version 1.0:将 access token 与 TLS 层的客户端密钥绑定。泄露后由于攻击者没有对应的私钥,就无法在 TLS 握手时完成身份证明。注意这个 RFC 已被弃用(historic):其理念没有问题,但底层所依赖的 Token Binding 协议(RFC 8472/8473)未能获得广泛支持和实现。
RFC 9449 OAuth 2.0 Demonstrating Proof-of-Possession:不依赖 TLS 客户端证书,而是通过应用层机制实现,是推荐的 PoP 实现方式。Client 自身持有一个私钥(通常非对称密钥对),公钥注册在 Authorization Server 上,access token 会内嵌该公钥的信息。Client 每次向 Resource Server 发起请求时,会用 Client 的私钥对本次规范化请求做签名来生成 JWT,这被称为 DPoP Proof。请求头里会同时带上 Authorization: Bearer <access_token> 和 DPoP: <DPoP Proof JWT>。
其他 RFC 最佳实践和 OAuth2.1 草案
RFC 6819 OAuth 2.0 Threat Model and Security Considerations:系统性地列举了 OAuth 2.0 协议在各种部署场景下可能面临的威胁模型,明确哪些设计是“危险的”(例如简化模式导致 access token 暴露,未检验 redirect URI 使得攻击者可以重定向至恶意端点,未将授权请求与响应绑定导致 CSRF 或授权码注入攻击等),并为每类威胁提供了“安全考虑”,成为后续 RFC 9700 Best Current Practice 的重要基础。
RFC 8252 OAuth 2.0 for Native Apps:对于 Mobile / Desktop 这种 Public Client 场景,明确禁止使用简化模式(因其返回 access token 到前端 URI,易被窃取),强制要求 Authorization Code Flow + PKCE,要求必须精确匹配 redirect URI(仅 Loopback IP 允许端口变化),同时规范了 redirect URI 的三种合法形式:
- 自定义 URI scheme(如
com.example.app:/callback),需采用反向域名以降低冲突风险。 - Loopback IP 地址(127.0.0.1 或 ::1)配合临时端口,适用于桌面或支持本地监听的移动平台。
- 通过 Android App Links 或 iOS Universal Links 实现的已声明的 HTTPS URI,由操作系统验证应用所有权。
RFC 8628 OAuth 2.0 Device Authorization Grant:为无浏览器或输入受限的客户端(如智能电视、打印机、IoT 设备)设计的授权流程。用户在另一台设备(如手机)上访问授权 URL 并输入用户码,授权服务器在后台轮询确认用户是否完成授权,成功后返回访问令牌。避免了在受限设备上直接处理浏览器重定向的困难。
RFC 8705 OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens:定义了两种安全机制:1. 客户端使用 mTLS 来替代 client_secret 向 Authorization Server 证明身份;2. 将访问令牌绑定到客户端证书(certificate-bound tokens)。即使令牌被窃取,攻击者也无法在无证书的情况下使用,实现令牌的发送方约束(sender-constraining)。适用于高安全要求的机密客户端(如微服务间通信)。
RFC 9101 JWT Secured Authorization Request:允许客户端将授权请求参数封装在一个经过签名和/或加密的 JWT 中,以 request(或 request_uri)参数的形式提交,保证授权请求的完整性与机密性。
RFC 9126 Pushed Authorization Requests:传统授权请求通过重定向 URI 的 query 参数传递,易受参数注入、请求对象篡改或日志泄露影响。PAR 要求客户端先通过安全后端通道(如 POST 到 Authorization Server)提交授权请求参数,Authorization Server 返回一个 request URI,客户端再用该 URI 发起授权请求。
RFC 9700 Best Current Practice for OAuth 2.0 Security:更新并扩展了早期 RFC(6749/6750/6819)中的威胁模型和安全建议,为现有 OAuth 2.0 实现提供一份权威、与时俱进的安全指南。
IETF Draft OAuth 2.0 for Browser-Based Apps:草案指出,纯前端应用不应再使用已废弃的 Implicit Flow,而是采用 Authorization Code Flow + PKCE 模式,同时提供三种安全架构方案:
- 前端 Token 存储:将 token 存于内存或严格限制的 HTTP-only、SameSite=Strict/Secure 的 Cookie 中,搭配短期令牌、严格的 CORS 策略、Content Security Policy (CSP) 以及定期检查 XSS 漏洞。
- Backend-for-Frontend 模式:前端只与自己的后端 BFF 通信,BFF 负责 OAuth 2.1 完整流程和敏感凭证。令牌通过安全会话 Cookie (SameSite=Strict/Lax) 从浏览器传到 BFF,BFF 再用它获取 access token。这样令牌永远不会暴露给浏览器 JavaScript 上下文,有效防御 XSS 威胁。
- Token Binding 增强:结合 RFC 9449 DPoP 技术,将 access token 绑定到应用实例生成的非对称密钥对上。即使 XSS 攻击者窃取了令牌,没有对应的私钥也无法生成有效的 DPoP Proofs,大大降低令牌泄露风险。
IETF Draft OAuth 2.1:OAuth 2.1 草案(目前是 2025-10-19 的 v2-1-14 版本)系统性地整合了过去十年最佳安全实践,吸收了 RFC 6749/6750/7636/8252/9700 等规范,形成更安全、更清晰的授权框架。
- 只保留授权码模式和机机场景的客户端凭证模式,完全移除 OAuth2.0 的简化模式和密码模式。
- 强制在所有客户端(包括 Confidential 和 Public)在授权码模式中使用 PKCE。
- 强制使用
state参数(或等效机制如 PKCE/nonce),防御 CSRF 和跨域注入攻击。 - 强制 Redirect URI 精确匹配,且禁止在 URI 查询参数中传输访问令牌。
- 强制刷新令牌绑定客户端,强烈推荐实现刷新令牌轮换(refresh token rotation)。
- 推荐使用 RFC 8414 OAuth 2.0 Authorization Server Metadata 实现客户端自动发现。
- 推荐使用 RFC 9068 JWT Profile for OAuth 2.0 Access Tokens 实现自包含与可验证性。
- 推荐对访问令牌实施发送方约束:浏览器应用使用 RFC 9449 DPoP,服务端通信使用 RFC 8705 mTLS。
- 推荐使用 RFC 9126 PAR 保护授权请求,复杂授权场景可结合 RFC 9101 JAR。
OIDC 和 UMA
SAML
SAML 2.0 在 2005 年发布,其核心定义在 OASIS 的 Assertions and Protocols 规范中,而非 RFC 中。









