3.2 OAuth 2.0 与 OIDC:委托授权不是登录捷径
OAuth 2.0 让资源所有者把有限 API 权限委托给 client,而不把自己的密码交给 client。OpenID Connect(OIDC)在 OAuth 之上增加身份认证语义。把 access token 当登录证明,或把 ID token 当 API 通行证,都会跨越协议边界。
先认清四个角色
Resource Owner:通常是用户
Client:请求被委托权限的应用
Authorization Server:认证用户并签发 token
Resource Server:持有受保护 APIClient ID 是公开标识,不是密码。浏览器 SPA 和原生应用无法可靠保守静态 client secret,属于 public client;后端服务能在受控环境保护凭据,才可能作为 confidential client。
Authorization Code + PKCE 是默认交互流程
简化流程:
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 仅保留在这一笔授权事务中,不写日志,也不发到授权端点。
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、OIDCnonce和 PKCE verifier 是否分别生成并验证?- Access token、ID token、refresh token 是否按受众与用途隔离?
- 是否固定可信 issuer、算法与 audience?
- Token 是否最小 scope、短期、可撤销或可检测重放?
- 浏览器脚本能否读取长期凭据?
- API 是否在 token 有效之外继续做资源级授权?
下一课用授权模型与资源级检查回答主体能对哪个具体资源做什么。
参考资料
- RFC 9700, Best Current Practice for OAuth 2.0 Security
- RFC 7636, Proof Key for Code Exchange
- OpenID Foundation, OpenID Connect Core 1.0
- IETF Datatracker, OAuth 2.1 Draft