Files
Qiniu/technical/sso/sso-oidc.md
T

5.3 KiB
Raw Blame History

tags, create time
tags create time
sso
oidc
jwt
auth
2026-07-13 10:03

OpenID Connect (OIDC)

概述

OpenID Connect(OIDC)是构建在 OAuth 2.0 之上的身份认证层。如果说 OAuth 2.0 解决「你能访问什么」,OIDC 则补充了「你是谁」。它是当前最主流的 SSO 协议,几乎所有现代身份提供商(Keycloak、Auth0、Okta、Azure AD)都支持 OIDC。

[!info] 为什么需要 OIDC? OAuth 2.0 的授权码拿到的是 access_token,它可以调 API,但你不知道登录的用户是谁。应用只能再去调 /userinfo 接口,流程不统一。OIDC 通过引入 ID Token 直接告诉应用用户的身份。

OIDC vs OAuth 2.0 的区别

维度 OAuth 2.0 OIDC
本质 授权框架 认证 + 授权
Token access_token, refresh_token + id_token (JWT)
用户信息 需要额外请求 /userinfo ID Token 自带 + /userinfo 补充
scope 自定义 必须含 openid,标准 scope: profile, email, address, phone
规范 RFC 6749 OpenID Connect Core 1.0

核心流程

OIDC 的授权码流程和 OAuth 2.0 几乎一致,区别在于 scope 包含 openid,返回值多了 id_token:

sequenceDiagram
    participant U as 用户
    participant App as Client (RP)
    participant IdP as OpenID Provider

    U->>App: 1. 点击登录
    App->>U: 2. 302 → IdP
    Note right of App: scope=openid profile email
    U->>IdP: 3. 认证 + 授权
    IdP->>U: 4. 302 → redirect_uri
    Note left of IdP: code=xxx & state=yyy
    U->>App: 5. 回调
    App->>IdP: 6. POST /token
    IdP-->>App: 7. { access_token, id_token, refresh_token }
    App->>App: 8. 验证 id_token 签名 + claims
    App-->>U: 9. 登录成功

ID Token 详解

ID Token 是一个 JWT(JSON Web Token),包含用户身份信息,由 IdP 用私钥签名。

结构

JWT 由三部分组成:Header.Payload.Signature

Header:

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "key-id-2026"
}

Payload(Claims):

{
  "iss": "https://idp.example.com",
  "sub": "user-12345",
  "aud": "your-client-id",
  "exp": 1752374580,
  "iat": 1752374280,
  "nonce": "random-nonce-value",
  "name": "张三",
  "email": "zhangsan@example.com",
  "email_verified": true,
  "picture": "https://cdn.example.com/avatar.jpg"
}

必须校验的 Claims

[!danger] 不校验 = 门户大开 每一个 Claim 都有其安全意义,跳过任何一个都可能导致身份伪造。

Claim 校验规则 不校验的风险
iss 必须是你配置的 IdP 地址 接受恶意 IdP 签发的 Token
aud 必须包含你的 client_id Token 被别的应用盗用
exp 必须未过期 过期 Token 仍可使用
nonce 必须和请求时一致 重放攻击
signature 用 IdP 公钥验证 Token 内容被篡改

校验流程

// 使用 OIDC 库自动校验 ID Token
provider, err := oidc.NewProvider(ctx, "https://idp.example.com")

verifier := provider.Verifier(&oidc.Config{
    ClientID: "your-client-id",
})

// 解析并校验 ID Token(签名、iss、aud、exp、nonce 全自动校验)
idToken, err := verifier.Verify(ctx, rawIDToken)

// 提取 Claims
var claims struct {
    Name    string `json:"name"`
    Email   string `json:"email"`
    Picture string `json:"picture"`
}
if err := idToken.Claims(&claims); err != nil {
    log.Fatal(err)
}

使用 coreos/go-oidc 库时,签名验证会自动从 IdP 的 JWKS 端点(/.well-known/jwks.json)拉取公钥并缓存。

OIDC Discovery

每个 OIDC Provider 都暴露一个发现端点,让客户端自动获取所有配置:

GET https://idp.example.com/.well-known/openid-configuration

返回:

{
  "issuer": "https://idp.example.com",
  "authorization_endpoint": "https://idp.example.com/oauth/authorize",
  "token_endpoint": "https://idp.example.com/oauth/token",
  "userinfo_endpoint": "https://idp.example.com/userinfo",
  "jwks_uri": "https://idp.example.com/.well-known/jwks.json",
  "scopes_supported": ["openid", "profile", "email"],
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "id_token_signing_alg_values_supported": ["RS256", "ES256"]
}

[!tip] Discovery 的好处 客户端只需要知道 IdP 的 URL,所有端点地址和算法支持都可以自动发现。这极大降低了接入成本。

常见陷阱与最佳实践

永远校验 ID Token 签名

  • 使用 IdP 公钥(通过 JWKS 获取)验证,不要只 Base64 解码就信了
  • 公钥轮换时,库应自动从 JWKS 端点刷新

nonce 必须使用

  • 登录请求生成 nonce 存入 session,ID Token 校验时比对
  • 防止 Token 被截获后重放

UserInfo Endpoint 的定位

  • ID Token 已包含基本身份信息,UserInfo 用于获取额外数据
  • 不要把敏感信息放在 ID Token 里(它可能被前端持有)

多租户场景的 hd 参数

  • Google OIDC 支持 hd(hosted domain)限制只允许特定域名登录
  • 企业 SSO 场景务必限制,防止个人账号混入

Key Rollover

  • IdP 会定期轮换签名密钥,客户端需要支持多 Key(通过 kid 匹配)
  • 推荐使用自动 JWKS 刷新的库,不要硬编码公钥