第三章:OpenID Connect¶
OpenID Connect (OIDC) 是基于 OAuth2 的身份认证协议,提供用户身份信息。
OAuth2 vs OIDC¶
┌─────────────────────────────────────────────────────────────┐
│ OAuth2 vs OIDC │
├─────────────────────────────────────────────────────────────┤
│ │
│ OAuth2 OIDC │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 授权协议 │ │ 认证协议 │ │
│ │ │ │ │ │
│ │ • 获取令牌 │ │ • 获取令牌 │ │
│ │ • 访问资源 │ │ • 用户身份 │ │
│ │ │ │ • ID Token │ │
│ │ │ │ • UserInfo │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ OAuth2: "这个应用能访问你的数据吗?" │
│ OIDC: "你是谁?" │
│ │
└─────────────────────────────────────────────────────────────┘
ID Token¶
ID Token 是 JWT 格式的身份令牌,包含用户身份信息。
ID Token 结构¶
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2F1dGguZXhhbXBsZS5jb20iLCJzdWIiOiJ1c2VyMTIzIiwiYXVkIjoiY2xpZW50X2lkIiwiZXhwIjoxNzA1MzE1ODAwLCJpYXQiOjE3MDUzMTIyMDAsIm5vbmNlIjoieHl6IiwiYXRfaGFzaCI6IjEyMzQ1NiIsImF1dGhfdGltZSI6MTcwNTMxMjIwMCwibmFtZSI6IkpvaG4gRG9lIiwiZW1haWwiOiJqb2huQGV4YW1wbGUuY29tIiwicGljdHVyZSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vcGljLmpwZyJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
ID Token Claims¶
{
"iss": "https://auth.example.com", // 签发者
"sub": "user123", // 用户唯一标识
"aud": "client_id", // 接收者(客户端ID)
"exp": 1705315800, // 过期时间
"iat": 1705312200, // 签发时间
"nonce": "xyz", // 防重放攻击
"at_hash": "123456", // Access Token 哈希
"auth_time": 1705312200, // 认证时间
"acr": "urn:mace:incommon:iap:silver", // 认证上下文
"amr": ["pwd", "mfa"], // 认证方法
"azp": "client_id", // 授权方
"name": "John Doe", // 姓名
"email": "john@example.com", // 邮箱
"email_verified": true, // 邮箱已验证
"picture": "https://example.com/pic.jpg", // 头像
"locale": "zh-CN" // 语言
}
OIDC 流程¶
发现端点¶
响应:
{
"issuer": "https://auth.example.com",
"authorization_endpoint": "https://auth.example.com/authorize",
"token_endpoint": "https://auth.example.com/token",
"userinfo_endpoint": "https://auth.example.com/userinfo",
"jwks_uri": "https://auth.example.com/.well-known/jwks.json",
"response_types_supported": ["code", "token", "id_token"],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["RS256"],
"scopes_supported": ["openid", "profile", "email"]
}
授权请求¶
GET /authorize?
response_type=code&
client_id=CLIENT_ID&
redirect_uri=https://client.example.com/callback&
scope=openid profile email&
state=xyz&
nonce=abc
令牌响应¶
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"id_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "dGhpcyBpcyBhIHJlZnJlc2g..."
}
UserInfo 端点¶
响应:
{
"sub": "user123",
"name": "John Doe",
"given_name": "John",
"family_name": "Doe",
"email": "john@example.com",
"email_verified": true,
"picture": "https://example.com/pic.jpg",
"locale": "zh-CN"
}
验证 ID Token¶
import jwt
import requests
from jwt import PyJWKClient
def verify_id_token(id_token, client_id, issuer):
"""验证 ID Token"""
# 获取 JWKS
jwks_url = f"{issuer}/.well-known/jwks.json"
jwks_client = PyJWKClient(jwks_url)
# 解码并验证签名
signing_key = jwks_client.get_signing_key_from_jwt(id_token)
payload = jwt.decode(
id_token,
key=signing_key.key,
algorithms=["RS256"],
audience=client_id,
issuer=issuer
)
return payload
# 使用
id_token = "eyJhbGciOiJSUzI1NiIs..."
payload = verify_id_token(id_token, "client_id", "https://auth.example.com")
print(payload["sub"]) # 用户 ID
print(payload["email"]) # 用户邮箱
OIDC Scope¶
| Scope | 说明 | 返回 Claims |
|---|---|---|
| openid | 必需 | sub |
| profile | 基本信息 | name, family_name, given_name, picture |
| 邮箱 | email, email_verified | |
| address | 地址 | address |
| phone | 电话 | phone_number, phone_number_verified |
| offline_access | 离线访问 | refresh_token |
Session 管理¶
检查 Session 状态¶
登出¶
前端登出¶
小结¶
OIDC 要点:
- ID Token:JWT 格式的身份令牌
- UserInfo:获取用户详细信息
- 发现端点:自动发现配置
- Scope:控制返回的用户信息
下一章我们将学习 JWT 详解。