第八章:OpenID Connect 身份认证
“OAuth 2.0 告诉你’这个人授权了’,OIDC 告诉你’这个人是谁’。”
mindmap
root((OpenID Connect))
核心概念
ID Token
UserInfo
Claims
流程
Authorization Code
Implicit
Hybrid
发现
Discovery
JWKS
提供商
Keycloak
Auth0
Okta
8.1 OIDC 与 OAuth 2.0 的关系
OIDC(OpenID Connect)是建立在 OAuth 2.0 之上的身份认证层:
┌─────────────────────────────────────┐
│ OpenID Connect │
│ (身份认证 — 你是谁?) │
│ ID Token, UserInfo, Claims │
├─────────────────────────────────────┤
│ OAuth 2.0 │
│ (授权 — 你能做什么?) │
│ Access Token, Scope, Grant │
├─────────────────────────────────────┤
│ HTTP / TLS │
└─────────────────────────────────────┘
维度 |
OAuth 2.0 |
OIDC |
|---|---|---|
目的 |
授权(访问资源) |
认证(确认身份) |
核心 Token |
Access Token |
ID Token |
用户信息 |
不标准 |
UserInfo Endpoint |
发现机制 |
无 |
.well-known/openid-configuration |
Scope |
自定义 |
openid, profile, email |
8.2 ID Token
ID Token 是一个 JWT,包含用户身份信息:
{
"iss": "https://auth.example.com",
"sub": "user-123",
"aud": "my-client-app",
"exp": 1709510400,
"iat": 1709506800,
"nonce": "abc123",
"name": "Walter Fan",
"email": "walter@example.com",
"email_verified": true,
"picture": "https://example.com/photo.jpg"
}
标准 Claims
Claim |
说明 |
必须 |
|---|---|---|
iss |
签发者 |
✅ |
sub |
用户唯一标识 |
✅ |
aud |
受众(Client ID) |
✅ |
exp |
过期时间 |
✅ |
iat |
签发时间 |
✅ |
nonce |
防重放攻击 |
条件必须 |
name |
用户全名 |
❌ |
邮箱 |
❌ |
|
picture |
头像 URL |
❌ |
8.3 OIDC 授权码流程
┌──────────┐ ┌──────────────┐
│ 用户 │ 1. 访问应用 │ │
│ (浏览器) │──────────────────▶│ Client │
│ │ │ (Web App) │
│ │ 2. 重定向到 IdP │ │
│ │◀──────────────────│ │
│ │ └──────────────┘
│ │ 3. 登录 + 授权
│ │──────────────────▶ ┌──────────────┐
│ │ │ IdP │
│ │ 4. 重定向回 Client│ (OIDC Provider)│
│ │ + auth code │ │
│ │◀──────────────────│ │
│ │ └──────┬───────┘
│ │ │
└────┬─────┘ │
│ │
▼ │
┌──────────────┐ 5. code → tokens │
│ Client │──────────────────────▶│
│ (后端) │ 6. id_token + │
│ │ access_token │
│ │◀──────────────────────│
│ │ │
│ │ 7. 验证 id_token │
│ │ 8. 获取 UserInfo │
│ │──────────────────────▶│
│ │ 9. 用户信息 │
│ │◀──────────────────────│
└──────────────┘
8.4 OIDC Discovery
每个 OIDC 提供商都有一个发现端点:
GET https://auth.example.com/.well-known/openid-configuration
{
"issuer": "https://auth.example.com",
"authorization_endpoint": "https://auth.example.com/authorize",
"token_endpoint": "https://auth.example.com/oauth/token",
"userinfo_endpoint": "https://auth.example.com/userinfo",
"jwks_uri": "https://auth.example.com/.well-known/jwks.json",
"scopes_supported": ["openid", "profile", "email"],
"response_types_supported": ["code", "id_token", "token id_token"],
"id_token_signing_alg_values_supported": ["RS256", "ES256"],
"subject_types_supported": ["public", "pairwise"]
}
8.5 OIDC 提供商对比
特性 |
Keycloak |
Auth0 |
Okta |
|
|---|---|---|---|---|
类型 |
开源自托管 |
SaaS |
SaaS |
SaaS |
协议 |
OIDC/SAML/OAuth2 |
OIDC/SAML |
OIDC/SAML |
OIDC |
定价 |
免费 |
免费层+付费 |
付费 |
免费 |
自定义 |
高 |
中 |
中 |
低 |
企业功能 |
完整 |
完整 |
完整 |
有限 |
部署 |
自托管/K8s |
云 |
云 |
云 |
8.6 Python OIDC 客户端
from authlib.integrations.starlette_client import OAuth
from starlette.config import Config
from fastapi import FastAPI, Request
from starlette.middleware.sessions import SessionMiddleware
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="session-secret")
oauth = OAuth()
oauth.register(
name='keycloak',
server_metadata_url='https://auth.example.com/realms/myrealm/.well-known/openid-configuration',
client_id='my-app',
client_secret='my-secret',
client_kwargs={'scope': 'openid profile email'},
)
@app.get('/login')
async def login(request: Request):
redirect_uri = request.url_for('auth_callback')
return await oauth.keycloak.authorize_redirect(request, redirect_uri)
@app.get('/callback')
async def auth_callback(request: Request):
token = await oauth.keycloak.authorize_access_token(request)
id_token = token.get('id_token')
userinfo = token.get('userinfo')
# 用户已认证
return {
"sub": userinfo["sub"],
"name": userinfo.get("name"),
"email": userinfo.get("email"),
}
8.7 小结
OIDC 是 OAuth 2.0 之上的身份认证层,提供标准化的用户身份信息
ID Token 是 JWT 格式,包含用户身份 Claims
Discovery 端点使客户端可以自动发现 OIDC 配置
Keycloak 是最流行的开源 OIDC 提供商,适合自托管场景
OIDC 是零信任架构中人类身份认证的首选协议