557 字
2 分钟
OAuth 2.0 与 OIDC 协议:授权与身份认证
前言
OAuth 2.0 和 OIDC 是现代 Web 身份认证的基础。本章详解授权码流程、JWT 结构、刷新令牌机制。
相关章节:
- TLS 与 HTTPS 协议 — OAuth 强制要求 HTTPS
- WebSocket 协议 — WebSocket 认证机制
- DNS 协议详解 — redirect_uri 域名验证
一、OAuth 2.0 授权码流程
1.1 四种授权模式对比
| 模式 | 适用场景 | 安全性 | 是否需要 client_secret |
|---|---|---|---|
| 授权码 | Web 服务端 | 是 | |
| 授权码 + PKCE | SPA/移动端 | 否 | |
| 客户端凭据 | 后端服务间 | 是 | |
| 隐式 (已废弃) | 旧 SPA | 否 | |
| 资源所有者密码 (已废弃) | 旧应用迁移 | 是 |
1.2 授权码流程详解
sequenceDiagram
participant U as 用户
participant C as 客户端 (App)
participant A as 授权服务器
participant R as 资源服务器
Note over U,R: 阶段 1: 授权请求
U->>C: 点击"使用 Google 登录"
C->>C: 生成 state (CSRF 防护)
C->>A: 302 重定向<br/>/authorize?client_id=xxx<br/>&redirect_uri=xxx<br/>&scope=openid profile<br/>&state=xyz
U->>A: 在授权服务器页面登录
Note over U,R: 阶段 2: 用户授权
A->>A: 验证用户身份
A->>U: 显示授权确认页面
U->>A: 点击"允许"
Note over U,R: 阶段 3: 获取令牌
A->>C: 302 重定向<br/>redirect_uri?code=abc&state=xyz
C->>C: 验证 state 匹配
C->>A: POST /token<br/>code=abc&client_id=xxx<br/>&client_secret=xxx
A->>A: 验证 code + client_secret
A->>C: access_token + refresh_token<br/>+ id_token (OIDC)
Note over U,R: 阶段 4: 访问资源
C->>R: GET /api/user<br/>Authorization: Bearer xxx
R->>R: 验证 access_token
R->>C: 返回用户数据
1.3 PKCE 流程 (移动端/SPA)
sequenceDiagram
participant App as 移动端 App
participant Auth as 授权服务器
Note over App: 1. 生成 code_verifier<br/>(随机 43-128 字符)
Note over App: 2. 计算 code_challenge<br/>= SHA256(code_verifier) Base64URL
App->>Auth: GET /authorize<br/>code_challenge=xxx<br/>code_challenge_method=S256
Auth->>App: 返回授权码 code
Note over App: 3. 用 code_verifier 换 token
App->>Auth: POST /token<br/>code=xxx<br/>code_verifier=原始值
Auth->>Auth: 验证: SHA256(verifier) == challenge
Auth->>App: access_token + refresh_token
二、JWT 令牌结构
2.1 JWT 组成结构
flowchart LR
subgraph JWT["JWT = Header.Payload.Signature"]
direction LR
H["Header<br/>{alg, typ}"] --> P["Payload<br/>{claims}"]
P --> S["Signature<br/>HMAC/RSA"]
end
subgraph Example["示例"]
E1["eyJhbGciOiJSUzI1NiJ9"] --> E2["eyJzdWIiOiIxMjM0In0"] --> E3["SflKxwRJSMeKKF2QT4fwp"]
end
2.2 JWT Header 标准
{ "alg": "RS256", // 签名算法: HS256, RS256, ES256 "typ": "JWT", // 令牌类型 "kid": "key-1" // 密钥 ID (用于密钥轮换)}2.3 JWT Payload 标准声明
| 声明 | 全称 | 说明 |
|---|---|---|
| iss | Issuer | 签发者 |
| sub | Subject | 用户唯一标识 |
| aud | Audience | 接收方 (client_id) |
| exp | Expiration | 过期时间 (Unix 时间戳) |
| iat | Issued At | 签发时间 |
| nbf | Not Before | 生效时间 |
| jti | JWT ID | 唯一标识 (防重放) |
2.4 JWT 验证代码
import jwtfrom jwt import PyJWKClient
# 1. 获取公钥 (从 JWKS 端点)jwks_client = PyJWKClient("https://auth.example.com/.well-known/jwks.json")signing_key = jwks_client.get_signing_key_from_jwt(token)
# 2. 解码验证claims = jwt.decode( token, signing_key.key, algorithms=["RS256"], audience="my_client_id", issuer="https://auth.example.com")
user_id = claims["sub"]三、Token 生命周期管理
3.1 Token 生命周期
stateDiagram-v2
[*] --> 获取令牌: 授权成功
获取令牌 --> Active: access_token (短期)
获取令牌 --> Stored: refresh_token (长期存储)
Active --> 使用中: API 请求
使用中 --> Active: 继续有效
使用中 --> Expired: 过期 (401)
Expired --> Active: 使用 refresh_token 刷新
Expired --> [*]: refresh_token 也过期
Active --> Revoked: 主动撤回
Revoked --> [*]
Stored --> Active: 刷新时使用
Stored --> [*]: 用户登出/撤回
note right of Active
有效期通常: 5分钟 - 1小时
end note
note right of Stored
有效期通常: 7天 - 30天
存储: HttpOnly Cookie
end note
3.2 Token 刷新策略
import time
class TokenManager: def __init__(self, refresh_threshold: int = 300): self.refresh_threshold = refresh_threshold # 提前 5 分钟刷新
def get_valid_token(self) -> str: # 1. 检查 access_token 是否即将过期 if self.access_token and not self._is_expiring_soon(): return self.access_token
# 2. 使用 refresh_token 刷新 if self.refresh_token: return self._refresh_access_token()
# 3. 需要重新授权 raise TokenExpiredError("请重新登录")
def _is_expiring_soon(self) -> bool: expires_at = self.token_claims.get("exp", 0) return time.time() > (expires_at - self.refresh_threshold)
def _refresh_access_token(self) -> str: response = httpx.post( f"{AUTH_SERVER}/oauth/token", data={ "grant_type": "refresh_token", "refresh_token": self.refresh_token, "client_id": CLIENT_ID, } ) self.access_token = response.json()["access_token"] return self.access_token3.3 Token 撤回 (RFC 7009)
# Token Introspection - 检查 token 状态def introspect_token(token: str) -> dict: response = httpx.post( f"{AUTH_SERVER}/oauth/introspect", data={"token": token}, auth=(CLIENT_ID, CLIENT_SECRET) ) return response.json() # {"active": true/false, ...}
# Token Revocation - 撤回 tokendef revoke_token(token: str, token_type: str = "access_token"): httpx.post( f"{AUTH_SERVER}/oauth/revoke", data={"token": token, "token_type_hint": token_type}, auth=(CLIENT_ID, CLIENT_SECRET) )四、OIDC 协议
4.1 OAuth 2.0 vs OIDC
flowchart TB
subgraph OAuth["OAuth 2.0 - 授权协议"]
O1["获取 Access Token"] --> O2["访问受保护资源"]
end
subgraph OIDC["OIDC = OAuth 2.0 + 身份层"]
I1["获取 Access Token"] --> I2["访问资源"]
I1 --> I3["获取 ID Token"]
I3 --> I4["验证用户身份"]
I4 --> I5["获取用户信息<br/>/userinfo 端点"]
end
style OIDC fill:#E8F5E9
4.2 OIDC 完整流程
sequenceDiagram
participant U as 用户
participant RP as Relying Party<br/>(客户端应用)
participant OP as OpenID Provider<br/>(授权服务器)
participant RS as 资源服务器
Note over U,RS: 1. 发现阶段
RP->>OP: GET /.well-known/openid-configuration
OP->>RP: 返回配置 (issuer, endpoints, keys)
Note over U,RS: 2. 授权请求 (scope 包含 openid)
RP->>OP: 302 /authorize?scope=openid profile email
U->>OP: 登录授权
OP->>RP: code
Note over U,RS: 3. 获取令牌
RP->>OP: POST /token (code + client_secret)
OP->>RP: access_token + id_token + refresh_token
Note over U,RS: 4. 验证 ID Token
RP->>RP: 1. 验证签名 (JWKS)
RP->>RP: 2. 验证 iss, aud, exp
RP->>RP: 3. 提取用户信息
Note over U,RS: 5. 获取更多用户信息 (可选)
RP->>OP: GET /userinfo<br/>Authorization: Bearer xxx
OP->>RP: 用户详细信息
4.3 三种令牌对比
| 令牌 | 用途 | 有效期 | 存储 |
|---|---|---|---|
| Access Token | 访问资源服务器 | 短 (1h) | 内存/Session |
| ID Token | 身份断言 | 短 (1h) | 验证后丢弃 |
| Refresh Token | 刷新 Access | 长 (7-30d) | HttpOnly Cookie |
4.4 ID Token Claims 示例
{ "iss": "https://auth.example.com", "sub": "user-123", "aud": "my-client-id", "exp": 1234567890, "iat": 1234567800, "auth_time": 1234567700, "nonce": "n-0S6_WzA2Mj", "acr": "urn:mace:incommon:iap:silver", "name": "张三", "email": "zhang@example.com", "email_verified": true, "picture": "https://example.com/avatar.jpg"}五、SSO 单点登录
5.1 会话管理
# Session vs Tokensession_cookie = request.session.get("user_id")# 或 JWT 存 Cookie5.2 常见问题
| 问题 | 解决方案 |
|---|---|
| CSRF | state 参数验证 |
| Token 泄露 | HTTPS + HttpOnly Cookie |
| 会话固定 | 登录后 Regenerate Session |
六、最佳实践
| 安全配置 | 说明 |
|---|---|
| HTTPS | 必须 |
| state 参数 | CSRF 防护 |
| PKCE | 移动端必需 |
| Token 短期 | Access Token 1h 内 |
| Refresh Token 单独存储 | 不放 LocalStorage |
七、总结
OAuth 2.0 + OIDC 构成现代身份认证基石,JWT + PKCE 是移动端最佳实践。
安全要点:
- Access Token 短期有效 (≤1小时)
- Refresh Token 存储在 HttpOnly Cookie
- 始终使用 PKCE (移动端/SPA 必需)
- state 参数防止 CSRF 攻击
- 所有通信必须通过 HTTPS
参考资料
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
OAuth 2.0 与 OIDC 协议:授权与身份认证
https://blog.souloss.com/posts/web/oauth2-and-oidc-protocols/ 部分信息可能已经过时
相关文章 智能推荐
1
NAT与中间盒:互联网的"不完美"现实
互联网运作 教科书说互联网是端到端的,现实是NAT和防火墙无处不在——NAPT工作原理、NAT如何破坏端到端、STUN/TURN/ICE穿透、防火墙/DPI/负载均衡器等中间盒,理解为什么"干净分层"是理想而非现实。
2
SSH 从入门到实践
工具 系统介绍 SSH 协议的历史演变、握手流程与 OpenSSH 的完整使用指南,涵盖客户端配置、公钥认证、端口转发、ssh-agent 密钥托管及服务端配置。
3
WebSocket 协议:双向通信
网络 深度解读 WebSocket 协议——握手流程、帧格式、心跳机制、代理穿透
4
OAuth 2.0 与 OpenID Connect
密码学与安全工程 深入 OAuth 2.0 与 OIDC——授权码/隐式/客户端凭证流程、PKCE、OIDC 身份层。
5
TLS 与 HTTPS 协议:加密传输
网络 深度解读 TLS/SSL 握手、加密传输、HSTS 与证书固定






