跳转至

第三章: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 流程

发现端点

GET /.well-known/openid-configuration HTTP/1.1
Host: auth.example.com

响应:

{
  "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 端点

GET /userinfo HTTP/1.1
Host: auth.example.com
Authorization: Bearer ACCESS_TOKEN

响应:

{
  "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, email_verified
address 地址 address
phone 电话 phone_number, phone_number_verified
offline_access 离线访问 refresh_token

Session 管理

检查 Session 状态

GET /session/status HTTP/1.1
Host: auth.example.com
Cookie: session_id=xxx

登出

GET /logout?
    client_id=CLIENT_ID&
    post_logout_redirect_uri=https://client.example.com/logout

前端登出

<iframe src="https://auth.example.com/session/end">
</iframe>

小结

OIDC 要点:

  • ID Token:JWT 格式的身份令牌
  • UserInfo:获取用户详细信息
  • 发现端点:自动发现配置
  • Scope:控制返回的用户信息

下一章我们将学习 JWT 详解。