跳到内容

第 8 卷 / 第 3 章 / 第 2 课

3.2 OAuth 2.0 与 OIDC:委托授权不是登录捷径 ​

OAuth 2.0 让资源所有者把有限 API 权限委托给 client,而不把自己的密码交给 client。OpenID Connect(OIDC)在 OAuth 之上增加身份认证语义。把 access token 当登录证明,或把 ID token 当 API 通行证,都会跨越协议边界。

先认清四个角色 ​

text
Resource Owner:通常是用户
Client:请求被委托权限的应用
Authorization Server:认证用户并签发 token
Resource Server:持有受保护 API

Client ID 是公开标识,不是密码。浏览器 SPA 和原生应用无法可靠保守静态 client secret,属于 public client;后端服务能在受控环境保护凭据,才可能作为 confidential client。

Authorization Code + PKCE 是默认交互流程 ​

简化流程:

text
client generates code_verifier
        └─ BASE64URL(SHA-256(ASCII(code_verifier))), no padding → code_challenge

browser → authorization endpoint
          response_type=code, client_id, exact redirect_uri, state,
          code_challenge, code_challenge_method=S256
          scope=openid, nonce  (OIDC)

authorization server → redirect with one-time code + state

client → token endpoint
         grant_type=authorization_code, code, code_verifier,
         client_id, redirect_uri; client authentication when required

在授权服务器正确绑定并验证 challenge、攻击者未取得 verifier 的前提下,PKCE 阻止仅截获授权码的人兑换 token。Verifier 必须为每次请求重新生成并保存在发起流程的客户端上下文,不能写死或降级到弱方法。

state 绑定发起请求与回调,防止 login CSRF 和响应混淆;OIDC 的 nonce 绑定 ID token 与认证请求,帮助防重放。本例保留两者用于请求关联。RFC 9700 也允许在确认服务端支持并正确实施 PKCE 等条件下,用 PKCE 承担 CSRF 防护;不能把这个有前提的替代规则理解成可以省略整个回调上下文检查。

Redirect URI 通常必须精确注册与匹配;原生应用的 loopback IP 回调端口有规范限定的例外,不能扩展成任意通配符。通配符、开放重定向和基于字符串前缀的宽松比较,会把 code/token 送到攻击者控制的位置。

实际检查 S256 编码 ​

S256 输出是 SHA-256 原始摘要的无填充 Base64url,不是十六进制摘要。下例使用 RFC 7636 附录 B 的公开测试向量;生成的 verifier 仅保留在这一笔授权事务中,不写日志,也不发到授权端点。

python
import base64
import hashlib
import re
import secrets

def pkce_challenge(verifier):
    if not isinstance(verifier, str) or not re.fullmatch(r"[A-Za-z0-9._~-]{43,128}", verifier):
        raise ValueError("invalid PKCE verifier")
    digest = hashlib.sha256(verifier.encode("ascii")).digest()
    return base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")

verifier = secrets.token_urlsafe(32)
challenge = pkce_challenge(verifier)
assert len(verifier) == len(challenge) == 43
assert pkce_challenge("dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk") == (
    "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
)

示例只验证生成与编码,不实现授权服务器。实际兑换还要验证授权码一次性、client 与 redirect_uri 绑定,以及 confidential client 所需的认证;PKCE 不是 client secret 的替代品。

OAuth 2.1 仍不是已发布 RFC ​

截至 2026 年 9 月 5 日,Datatracker 列出的 OAuth 2.1 draft-16 仍是 IETF Internet-Draft。它汇总 Authorization Code + PKCE、移除 implicit 和 resource owner password credentials 等现代实践,但不能把草案版本写成最终标准。

生产安全基线应以已发布的 RFC 9700(OAuth 2.0 Security Best Current Practice)等规范为依据:不使用 implicit grant,不使用 Resource Owner Password Credentials grant,防止 authorization code 注入和 token replay,并限制 token 权限。

Token 类型决定验证位置 ​

Access token 是给 Resource Server 的授权凭据,可以是不透明随机值,也可以是 JWT。Client 不应依赖它的内部字段作为用户登录状态。

ID token 是 OIDC Client 的认证结果,目标 audience 是 Client。验证至少包括:

  • 从受信配置确定 issuer,而不是相信 token 自报地址;
  • 只允许预期算法,按 issuer 的可信 key set 验签;
  • 校验 iss、aud 包含本 Client ID,并按多受众及 azp 出现情况检查 authorized party;
  • 校验 exp、iat 与合理时钟偏差;
  • 对应请求中校验 nonce;
  • 根据业务检查 auth_time、acr 等保证信息。

Resource Server 验证 access token 时则关注 issuer、自己的 audience、scope/authorization details、有效期、撤销策略和 sender constraint。它不应把“JWT 签名有效”直接等同于“允许访问任意对象”。

Refresh Token 是高价值长期凭据 ​

浏览器和移动端的 refresh token 泄露后可能持续换取新 access token。RFC 9700 要求授权服务器对 public client 的 refresh token 采用轮换或发送方约束,以发现重放。轮换时签发新 token 并作废旧 token,还要保留 token family 关系并定义重放后的撤销策略;正常并发刷新也需明确处理,避免误把竞态当成盗用。

Token 应遵循最小 audience、最小 scope 和短有效期。高价值系统可使用 mTLS 或 DPoP 将 access token 绑定到发送方密钥,降低 bearer token 被复制后的可用性,但这仍不能修复终端已完全失陷的情况。

前端应用应避免把长期 token 放入任意脚本可读的 localStorage。对传统 Web 产品,Backend-for-Frontend 可以把 OAuth token 留在服务端,用受保护 session Cookie 暴露最小浏览器会话面。选择取决于架构,不能把 BFF 当万能代理而忽略 CSRF、会话固定和后端横向权限。

OIDC Discovery 也需要信任起点 ​

Discovery 与 JWKS 自动化 key 发现和轮换,但 URL 必须来自预配置 issuer 或经过严格验证的发现流程。不能从任意 token 的 iss 直接发网络请求,否则可能造成 SSRF、密钥混淆和恶意 issuer 接管。

Key rotation 要允许新旧签名 key 短暂重叠,并对未知 kid 做受限刷新;攻击者可构造大量随机 kid,不能让每次失败都无限触发外部 JWKS 请求。

流程检查 ​

  • 使用 Authorization Code + PKCE(S256),而非 implicit/ROPC?
  • Redirect URI 是否精确注册,没有开放重定向?
  • state、OIDC nonce 和 PKCE verifier 是否分别生成并验证?
  • Access token、ID token、refresh token 是否按受众与用途隔离?
  • 是否固定可信 issuer、算法与 audience?
  • Token 是否最小 scope、短期、可撤销或可检测重放?
  • 浏览器脚本能否读取长期凭据?
  • API 是否在 token 有效之外继续做资源级授权?

下一课用授权模型与资源级检查回答主体能对哪个具体资源做什么。

参考资料 ​

Built with VitePress | Software Systems Atlas