--- tags: [sso, oidc, jwt, auth] create time: 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`: ```mermaid 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:** ```json { "alg": "RS256", "typ": "JWT", "kid": "key-id-2026" } ``` **Payload(Claims):** ```json { "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 内容被篡改 | ### 校验流程 ```go // 使用 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 ``` 返回: ```json { "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 刷新的库,不要硬编码公钥