OAuth2 深入详解:从概念、流程到工程落地
很多人第一次学 OAuth2,都会陷入一个误区: 把它当成“登录协议”去背流程图。
实际上,OAuth2 首先是一个授权框架,核心目标是: 在不暴露用户密码的前提下,把“有限、可撤销、可审计”的访问权限授予客户端。
这篇文章尝试把 OAuth2 讲透:
- 它解决了什么问题,不解决什么问题。
- 关键角色、令牌与端点。
- 各授权模式的适用场景与淘汰情况。
- 现代最佳实践(PKCE、刷新令牌轮换、最小权限等)。
- 在微服务体系中的常见架构与踩坑点。
1. OAuth2 到底解决了什么问题
先看一个常见需求:
你做了一个日历应用,希望用户授权你读取他在某云厂商里的日程。
最原始的方式是让用户把账号密码给你。这是灾难:
- 你的系统能拿到用户全部权限。
- 密码泄漏会影响用户所有服务。
- 用户几乎无法只撤销“你这个应用”的权限。
OAuth2 的思路是引入“令牌(token)”作为能力凭证:
- 用户只在授权服务器登录。
- 客户端拿到的是访问令牌,不是用户密码。
- 令牌可以限制范围(scope)、有效期、受众(audience)。
- 令牌可失效、可撤销、可追踪。
一句话总结: OAuth2 的本质是受控委托授权(delegated authorization)。
2. 先记住四个角色
RFC 6749 里定义了四个核心角色:
- Resource Owner(资源所有者):通常是用户。
- Client(客户端):请求资源的应用。
- Authorization Server(授权服务器):签发令牌。
- Resource Server(资源服务器):托管 API 资源并校验令牌。
现实里,授权服务器和资源服务器可能是同一个系统,也可能分离。
一个最小心智模型
- 用户同意授权。
- 客户端拿到访问令牌(Access Token)。
- 客户端带令牌访问 API。
- API 验证令牌后返回数据。
注意: OAuth2 不规定“用户怎么登录”(密码、生物识别、MFA 都可),它只关心授权与令牌流转。
3. 几个关键对象:code、token、scope
3.1 Authorization Code(授权码)
短期、一次性凭证,用来在后端换取令牌。 它本身不是访问资源的凭证。
3.2 Access Token(访问令牌)
调用 API 的凭证,通常生命周期较短(例如 5 到 30 分钟)。
3.3 Refresh Token(刷新令牌)
用于换取新的 Access Token,生命周期更长,必须更严格保护。
3.4 Scope(权限范围)
用来约束令牌权限,例如:
read:profilewrite:postcalendar.readonly
scope 不是越多越好,而是越小越安全。
4. OAuth2 的核心流程:授权码模式
如果只学一个流程,就学 Authorization Code Grant + PKCE。 这是今天绝大多数有用户参与场景的首选方案(Web、SPA、移动端都可用)。
4.1 标准流程(简化)
- 客户端把用户重定向到授权服务器
/authorize。 - 用户登录并同意授权。
- 授权服务器重定向回客户端,并附带
code。 - 客户端通过后端调用
/token,用code换取access_token(和可选refresh_token)。 - 客户端调用资源服务器 API。
4.2 为什么要 PKCE
PKCE(Proof Key for Code Exchange)用来防止授权码被截获后重放。
它的核心机制:
- 客户端先生成随机
code_verifier。 - 计算
code_challenge = BASE64URL(SHA256(code_verifier))。 - 在
/authorize请求里带上code_challenge。 - 在
/token换令牌时提交原始code_verifier。 - 授权服务器校验两者是否匹配。
即使攻击者偷到 code,没有 code_verifier 也换不到 token。
5. 各授权模式怎么选
5.1 Authorization Code Grant(推荐)
场景:有用户参与的登录/授权,Web、SPA、移动端。
结论:默认选它,并启用 PKCE。
5.2 Client Credentials Grant(推荐,机对机)
场景:服务到服务(M2M),没有用户。
特点:令牌代表应用自身身份,而不是某个用户。
5.3 Device Authorization Grant(设备码模式)
场景:输入能力受限设备(TV、CLI 设备、IoT)。
特点:设备显示 user_code,用户在手机/PC 完成授权。
5.4 Refresh Token Grant(配套能力)
严格来说它是“换 token 的机制”,不是用户授权入口。
5.5 已不建议使用的模式
- Implicit Grant:历史上用于纯前端 SPA,如今已不推荐,改用授权码 + PKCE。
- Resource Owner Password Credentials(密码模式):除极少数遗留系统外应避免。
6. 各模式时序图(重点)
下面用 Mermaid 时序图把每种模式的关键交互画出来。为了简化可读性,省略了部分错误分支和协议细节。
6.1 授权码模式(Authorization Code + PKCE)
sequenceDiagram
actor U as User(Browser)
participant C as Client(App/BFF)
participant AS as Authorization Server
participant RS as Resource Server
U->>C: 点击“使用账号登录”
C->>U: 302 跳转到 /authorize
U->>AS: 登录 + 同意 scope
AS->>U: 302 回调 redirect_uri?code=...&state=...
U->>C: 携带 code 返回客户端回调
C->>AS: POST /token\n grant_type=authorization_code\n code=...\n code_verifier=...
AS->>C: access_token (+ refresh_token)
C->>RS: Authorization: Bearer access_token
RS->>C: 200 Protected Resource
详细说明:
state用于防 CSRF,回调时必须逐字校验。- PKCE 的
code_verifier只在客户端本地保存,攻击者即便截获code也无法兑换 token。 - 公网客户端(SPA/移动端)也推荐走这个模式并强制 PKCE。
6.2 客户端凭证模式(Client Credentials)
sequenceDiagram
participant C as Client(Service A)
participant AS as Authorization Server
participant RS as Resource Server(Service B)
C->>AS: POST /token\n grant_type=client_credentials\n client_id/client_secret(或 mTLS/private_key_jwt)
AS->>C: access_token
C->>RS: Authorization: Bearer access_token
RS->>C: 200 API Response
详细说明:
- 该模式没有用户参与,token 代表应用本身而非用户。
- scope 通常是服务级权限,例如
payment.read。 - 客户端认证建议优先
private_key_jwt或 mTLS,避免长期静态 secret。
6.3 设备码模式(Device Authorization Grant)
sequenceDiagram
participant D as Device(TV/CLI)
actor U as User(Phone/PC)
participant AS as Authorization Server
participant RS as Resource Server
D->>AS: POST /device_authorization (client_id, scope)
AS->>D: device_code, user_code, verification_uri, interval
D->>U: 展示 user_code + verification_uri
U->>AS: 打开 verification_uri 并输入 user_code
U->>AS: 登录 + 同意授权
loop 按 interval 轮询
D->>AS: POST /token\n grant_type=urn:...:device_code\n device_code=...
AS-->>D: authorization_pending / slow_down / access_token
end
D->>RS: Authorization: Bearer access_token
RS->>D: 200 Protected Resource
详细说明:
- 轮询必须遵循
interval,否则会被slow_down。 user_code需要短期有效并支持失败次数限制。- 很适合电视、机顶盒、命令行等弱输入场景。
6.4 刷新令牌流程(Refresh Token Grant)
sequenceDiagram
participant C as Client
participant AS as Authorization Server
participant RS as Resource Server
C->>RS: 旧 access_token 调用 API
RS-->>C: 401 invalid_token
C->>AS: POST /token\n grant_type=refresh_token\n refresh_token=...
AS->>C: new access_token (+ new refresh_token)
C->>RS: Authorization: Bearer new_access_token
RS->>C: 200 API Response
详细说明:
- 强烈建议 Refresh Token Rotation:每次刷新都签发新 refresh token。
- 服务端应检测 refresh token 重放,一旦发现复用立即吊销 token 家族。
- refresh token 应仅在高信任存储中保存,避免暴露到浏览器可读环境。
6.5 隐式模式(Implicit,不推荐)
sequenceDiagram
actor U as User(Browser)
participant C as Client(SPA)
participant AS as Authorization Server
participant RS as Resource Server
U->>C: 点击登录
C->>U: 302 跳转 /authorize?response_type=token
U->>AS: 登录 + 同意
AS->>U: 302 redirect_uri#access_token=...
U->>C: URL fragment 暴露给前端脚本
C->>RS: Authorization: Bearer access_token
RS->>C: 200 API Response
为何不推荐:
- token 直接暴露在浏览器环境,攻击面大。
- 无法安全使用 refresh token(历史上通常不给)。
- 现代实践已由授权码 + PKCE 全面替代。
6.6 密码模式(ROPC,不推荐)
sequenceDiagram
actor U as User
participant C as Client
participant AS as Authorization Server
participant RS as Resource Server
U->>C: username + password
C->>AS: POST /token\n grant_type=password\n username/password
AS->>C: access_token (+ refresh_token)
C->>RS: Authorization: Bearer access_token
RS->>C: 200 API Response
为何不推荐:
- 客户端接触用户凭据,违背最小暴露原则。
- 无法引入现代认证能力(MFA、无密码、风险控制)的一致体验。
- 仅适用于极少数强信任遗留场景,且应尽快迁移。
7. Token 形态:JWT vs Opaque
Access Token 常见两种形态:
- JWT(自包含):资源服务器可本地验签和读取 claims。
- Opaque Token(不透明令牌):资源服务器通过 introspection 向授权服务器查询。
JWT 的优缺点
优点:
- 资源服务器可离线校验,性能好。
- 减少对授权服务器实时依赖。
缺点:
- 撤销即时生效困难(通常依赖短过期 + 黑名单策略)。
- 容易被误用为“会话存储”。
Opaque Token 的优缺点
优点:
- 服务端可集中控制,撤销更直接。
- 客户端和 API 看不到内部结构,泄露信息更少。
缺点:
- 依赖 introspection 可用性与延迟。
- 高并发下要做缓存和限流。
实践上没有绝对优劣,取决于你的实时撤销需求、网络拓扑和性能目标。
8. OAuth2 不是认证协议,但常与 OIDC 一起用
很多系统会说“OAuth 登录”。 严格来说,OAuth2 只负责授权。
如果你需要标准化“用户身份信息(是谁)”,应使用 OpenID Connect(OIDC):
- 在 OAuth2 上增加
id_token。 - 定义用户信息端点与标准 claims。
- 明确认证语义(登录时间、认证强度等)。
一句话:
- OAuth2:你可以做什么。
- OIDC:你是谁。
9. 安全基线与高频踩坑
下面这些是工程里最常见的坑。
8.1 必做清单
- 全链路 HTTPS。
- 授权码模式开启 PKCE。
- 校验
state防 CSRF。 - 严格匹配
redirect_uri(精确匹配,不做模糊匹配)。 - Access Token 短时效。
- Refresh Token 轮换(rotation)+ 重放检测。
- 最小权限 scope,按资源拆分 audience。
- 客户端密钥、签名私钥托管在安全存储(KMS/HSM/密钥管理系统)。
8.2 常见错误
- 把 token 放在 URL query(会泄漏到日志、历史、Referer)。
- 在前端长期存储高价值 token(如 localStorage 永久存储 refresh token)。
- 资源服务器不校验
iss、aud、exp、签名算法。 - 接受任意重定向地址,导致开放重定向和 code/token 泄漏。
- 把 JWT 当数据库,塞入过多敏感字段。
10. 在微服务里的典型落地
一个常见架构:
- 边界层(API Gateway / BFF)负责与授权服务器交互。
- 下游服务只接受内部标准化身份上下文。
- 对外 token 在边界校验和转换,减少横向扩散。
两种校验策略
- 网关集中校验:实现统一、策略一致,但网关压力更大。
- 服务自治校验:服务独立、弹性更好,但一致性治理成本更高。
很多团队采用折中方案:
- 网关做粗粒度鉴权和限流。
- 服务内部做细粒度授权(资源级别 RBAC/ABAC)。
11. 一个实战请求序列(授权码 + PKCE)
为了方便理解,下面给一个简化示例。
10.1 发起授权请求
GET /authorize?
response_type=code&
client_id=blog-web&
redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback&
scope=openid%20profile%20read%3Apost&
state=af0ifjsldkj&
code_challenge=QmFzZTY0VVJMU0hBMjU2Li4u&
code_challenge_method=S256
10.2 后端换 token
curl -X POST https://auth.example.com/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=SplxlOBeZQQYbYS6WxSbIA" \
-d "redirect_uri=https://app.example.com/callback" \
-d "client_id=blog-web" \
-d "code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
10.3 带 token 调用 API
curl https://api.example.com/posts \
-H "Authorization: Bearer eyJhbGciOi..."
12. 选型建议(速查版)
- 用户登录和第三方授权:授权码 + PKCE。
- SPA:仍然是授权码 + PKCE,必要时采用 BFF 降低 token 暴露面。
- 移动端:授权码 + PKCE,使用系统浏览器/ASWebAuthenticationSession。
- 服务间调用:Client Credentials。
- 大规模撤销诉求强:优先考虑 Opaque + introspection。
- 高性能、低延迟内部调用:可考虑 JWT,但要控制过期时间和撤销策略。
13. 总结
OAuth2 的难点不在于“记住几个 grant type”,而在于建立正确的安全边界:
- 令牌是能力,不是身份本身。
- 授权是最小化、可撤销、可审计。
- 客户端、授权服务器、资源服务器职责要清晰。
- 现代实践里,授权码 + PKCE 是主流基线。
如果你把 OAuth2 当成“分布式系统里的权限流转协议”,很多细节都会变得自然: 为什么要短 token、为什么要 scope、为什么要 rotate refresh token、为什么要强调 redirect_uri 精确匹配。
当这些原则成为默认习惯时,你的 IAM 体系会比“能跑就行”的实现稳健很多。