mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
557 字
2 分钟
OAuth 2.0 与 OIDC 协议:授权与身份认证
2022-11-18

前言#

OAuth 2.0 和 OIDC 是现代 Web 身份认证的基础。本章详解授权码流程、JWT 结构、刷新令牌机制。

相关章节

一、OAuth 2.0 授权码流程#

1.1 四种授权模式对比#

模式适用场景安全性是否需要 client_secret
授权码Web 服务端
授权码 + PKCESPA/移动端
客户端凭据后端服务间
隐式 (已废弃)旧 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 标准声明#

声明全称说明
issIssuer签发者
subSubject用户唯一标识
audAudience接收方 (client_id)
expExpiration过期时间 (Unix 时间戳)
iatIssued At签发时间
nbfNot Before生效时间
jtiJWT ID唯一标识 (防重放)

2.4 JWT 验证代码#

import jwt
from 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_token

3.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 - 撤回 token
def 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 Token
session_cookie = request.session.get("user_id")
# 或 JWT 存 Cookie

5.2 常见问题#

问题解决方案
CSRFstate 参数验证
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/
作者
Souloss
发布于
2022-11-18
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时