OpenID Connect¶
OpenID Connect(OIDC)是构建在 OAuth2 之上的身份认证层。如果说 OAuth2 解决了"应用能做什么",那么 OIDC 解决的是"用户是谁"。
本文你会学到:
- OIDC 与 OAuth2 的核心区别(授权 vs 认证)
- ID Token 的标准声明和验证步骤
- UserInfo 端点的使用场景
- OIDC 发现文档的作用
- OIDC 授权码流程与普通 OAuth2 授权码流程的差异
- Hybrid Flow 的三种
response_type与适用场景 - OIDC 的会话管理与三种登出机制
🔗 OIDC 与 OAuth2 的关系¶
关键区别
| 协议 | 解决的问题 | 核心令牌 |
|---|---|---|
| OAuth2 | 授权:该应用有权限访问哪些资源? |
Access Token |
| OpenID Connect | 认证:当前登录的用户是谁? |
ID Token |
OIDC 并不替代 OAuth2,而是在 OAuth2 授权码流程的基础上,额外颁发一个 ID Token 来携带用户身份信息。使用 OIDC 时,OAuth2 的所有机制都保持不变。
OIDC 的三种流程:
Authorization Code Flow(最常用,推荐)- Implicit Flow(已废弃)
- Hybrid Flow(部分场景使用)
🪪 ID Token¶
ID Token 就像一张**电子身份证**——它是 JWT 格式的令牌,里面包含已认证用户的身份信息(姓名、邮箱等)。和身份证一样,它有签发机构(iss)、有效期(exp)、持有人(sub),并且可以通过签名验证真伪。
ID Token 是 OIDC 的核心,是一个 JWT 格式的令牌(JWT 结构详见 JWT 令牌),包含已认证用户的身份信息。
标准 Claim(声明)¶
| Claim | 含义 | 是否必须 |
|---|---|---|
sub |
用户唯一标识符(Subject),在授权服务器范围内唯一 | ✅ 必须 |
iss |
ID Token 颁发者的 URL(Issuer) | ✅ 必须 |
aud |
接收方,通常是客户端的 Client ID(可以是字符串或数组,多受众时为数组) | ✅ 必须 |
exp |
过期时间(Unix 时间戳) | ✅ 必须 |
iat |
颁发时间(Unix 时间戳) | ✅ 必须 |
nonce |
客户端请求时传入的随机值,防重放攻击 | 若请求中有则必须 |
name |
用户全名 | 可选(profile scope) |
email |
用户邮箱 | 可选(email scope) |
picture |
用户头像 URL | 可选(profile scope) |
ID Token 示例(解码后的 Payload):
ID Token 验证步骤¶
客户端收到 ID Token 后必须验证:
- 验证签名(使用授权服务器的公钥,通过 JWKS URI 获取)
- 验证
iss与预期授权服务器一致 - 验证
aud包含当前客户端的 Client ID - 验证
exp未过期 - 若请求时带了
nonce,验证nonce与请求值一致
👤 UserInfo 端点¶
ID Token 在登录时一次性颁发,里面的信息是登录那一刻的快照。如果用户后来改了邮箱呢?这时就需要 UserInfo 端点——它可以随时查询最新的用户信息。
UserInfo 端点是授权服务器提供的 API,客户端携带 Access Token 即可获取用户详细信息。
响应示例:
ID Token vs UserInfo
- ID Token 在登录时一次性颁发,适合存储不经常变化的基础身份信息
- UserInfo 端点可以随时查询最新用户信息,适合需要实时数据的场景
📋 OIDC 发现文档¶
还记得核心概念里讲的 JWKS 端点吗?发现文档就是它的升级版——不是一个端点的地址,而是所有端点的地址集合,外加服务器的能力声明。客户端访问一个固定的 URL 就能拿到全部配置,不用逐个硬编码。
支持 OIDC 的授权服务器必须在标准路径发布发现文档(Discovery Document):
发现文档包含授权服务器的所有端点地址和能力声明,客户端无需硬编码这些地址:
🔄 OIDC 授权码流程(与 OAuth2 的差异)¶
如果你已经理解了 OAuth2 的授权码流程,那么 OIDC 版本只是在三个地方做了扩展——
- 请求中必须包含
openidscope:这是触发 OIDC 模式的关键 - Token 端点额外返回
id_token - 可选
nonce参数:防止 ID Token 重放攻击
sequenceDiagram
participant 用户浏览器
participant 客户端服务端
participant 授权服务器
participant 资源服务器
用户浏览器->>授权服务器: 1. 重定向至授权端点<br/>?response_type=code&scope=openid profile email&nonce=xyz&state=...
授权服务器-->>用户浏览器: 2. 展示登录/授权页面
用户浏览器->>授权服务器: 3. 用户登录并同意授权
授权服务器-->>用户浏览器: 4. 重定向回 redirect_uri(携带授权码)
用户浏览器->>客户端服务端: 5. 转发授权码
客户端服务端->>授权服务器: 6. POST /token(授权码换 Token)
授权服务器-->>客户端服务端: 7. Access Token + Refresh Token + **ID Token**(含 nonce=xyz)
Note over 客户端服务端: 验证 ID Token 签名、iss、aud、exp、nonce
客户端服务端->>资源服务器: 8. GET /api/resource<br/>Authorization: Bearer <access_token>
资源服务器-->>客户端服务端: 9. 返回受保护资源
执行过程说明:
发起 OIDC 授权请求:与普通 OAuth2 授权码流程的关键区别在于scope参数**必须包含openid**,这是告知授权服务器启用 OIDC 模式的信号。可额外附加profile、email等 scope 来请求用户资料信息。nonce是客户端生成的随机值,用于防止 ID Token 重放攻击。展示授权页面:授权服务器展示登录/同意页面,用户认证过程完全在授权服务器侧进行。用户完成认证授权:用户登录并同意授权。由于openidscope 的存在,授权服务器知道此次需要返回身份信息。颁发授权码:与标准 OAuth2 流程相同,返回一次性短效授权码。转发授权码:浏览器访问回调地址,客户端服务端取出授权码,并验证state防 CSRF。凭码换 Token:客户端服务端在后端向 Token 端点发起请求,流程与 OAuth2 相同。额外颁发 ID Token:OIDC 的核心差异在于 Token 端点除了返回 Access Token 和 Refresh Token,还额外返回 ID Token。ID Token 是一个 JWT,内嵌用户身份信息(sub、email、name等)和安全验证字段(iss、aud、exp、nonce)。客户端服务端必须完整验证 ID Token(见ID Token 验证步骤),确认其来自可信授权服务器且是对本次请求的响应(nonce 一致性检验防重放)。携带 Access Token 访问受保护 API:获取用户身份后,客户端可用 Access Token 正常访问资源服务器。ID Token 用于认证(知道用户是谁),Access Token 用于授权(访问受保护的资源)。返回资源:资源服务器验证 Access Token 后返回数据。
🏷️ 标准 Scope 对应的 Claim¶
| Scope | 包含的 Claim |
|---|---|
openid |
sub(必须包含此 scope 才触发 OIDC) |
profile |
name、given_name、family_name、picture、locale 等 |
email |
email、email_verified |
address |
address(结构化地址对象) |
phone |
phone_number、phone_number_verified |
Hybrid Flow¶
前面介绍了 Authorization Code Flow 和 Implicit Flow。OIDC 还定义了 Hybrid Flow——它结合两者的优势:Token 通过后端通道(Token 端点)获取(更安全),ID Token 在前端通道返回(减少一次往返)。就像既使用了快递代取(安全性)又附带了短信通知(即时性)。
response_type 与流程类型¶
response_type 值 |
流程类型 |
|---|---|
code |
Authorization Code Flow |
id_token |
Implicit Flow |
id_token token |
Implicit Flow |
code id_token |
Hybrid Flow |
code token |
Hybrid Flow |
code id_token token |
Hybrid Flow |
三种流程对比¶
| 属性 | Authorization Code | Implicit | Hybrid |
|---|---|---|---|
| 所有令牌从 Token 端点返回 | 是 | 否 | 否 |
| 令牌不经过 User Agent | 是 | 否 | 是 |
| 客户端可被认证 | 是 | 否 | 是 |
| 支持 Refresh Token | 是 | 否 | 是 |
| 单次往返完成 | 否 | 是 | 否 |
Hybrid Flow 执行过程¶
以 response_type=code id_token 为例:
sequenceDiagram
participant 用户浏览器
participant 客户端服务端
participant 授权服务器
用户浏览器->>授权服务器: 1. 重定向至授权端点<br/>?response_type=code+id_token&scope=openid+profile&nonce=xyz&state=...
授权服务器-->>用户浏览器: 2. 展示登录/授权页面
用户浏览器->>授权服务器: 3. 用户登录并同意授权
授权服务器-->>用户浏览器: 4. 重定向回 redirect_uri<br/>携带 id_token + code
用户浏览器->>客户端服务端: 5. 转发 id_token + code
客户端服务端->>授权服务器: 6. POST /token(授权码换 Token)
授权服务器-->>客户端服务端: 7. Access Token + Refresh Token
Note over 客户端服务端: 验证 ID Token(含 c_hash 绑定)
执行过程说明:
发起 Hybrid 授权请求:response_type使用code id_token,告知授权服务器同时返回 ID Token 和 Authorization Code。必须包含nonce参数。展示授权页面:与 Authorization Code Flow 相同。用户完成认证授权:用户登录并同意。同时返回 ID Token 和 Code:与 Authorization Code Flow 不同,授权端点在重定向响应中直接返回 ID Token(URL Fragment 或 Query 中),同时返回 Authorization Code。注意:Access Token 不会在前端通道返回,必须通过 Token 端点获取。转发到客户端服务端:客户端提取 ID Token 和 Authorization Code,进行后续处理。凭码换 Token:与标准授权码流程相同,客户端服务端用 Authorization Code 向 Token 端点换取 Access Token 和 Refresh Token。验证 ID Token 中的 c_hash:由于 Authorization Code 经过了浏览器,客户端需要验证 ID Token 中的c_hash声明,确认返回的 Code 确实由授权服务器颁发给这个 ID Token 的。
ID Token 的 c_hash 和 at_hash 声明¶
Hybrid Flow 中 ID Token 必须包含以下声明,以绑定 Authorization Code 和 Access Token:
c_hash:Authorization Code 的 SHA-256 哈希值的前半部分(base64url 编码)at_hash(如果 Access Token 同时在授权响应中返回):Access Token 的 SHA-256 哈希值的前半部分
c_hash和at_hash是 Hybrid Flow 的核心安全机制。它们确保 ID Token 中返回的 code 和 token 是合法颁发给这个 ID Token 的,防止令牌替换攻击。
为什么 Hybrid Flow 需要 c_hash?
在 Hybrid Flow 中,Authorization Code 经过了用户浏览器(前端通道),存在被篡改的风险。c_hash 将 ID Token(由授权服务器签名,无法伪造)与 Authorization Code 绑定在一起,客户端可以验证收到的 Code 确实是授权服务器颁发的,而不是攻击者替换的。
会话管理(Session Management)¶
OIDC 提供了标准的会话管理机制,允许客户端(RP)实时感知用户在 OP(OpenID Provider)端的登录状态。
OP Session 状态检查¶
授权服务器的发现文档中包含 check_session_iframe 字段,指向一个可嵌入的 iframe 页面。客户端通过以下方式轮询 OP 的会话状态:
- 在页面中嵌入 OP 的
check_session_iframe - 向 iframe 发送
postMessage,包含client_id和当前页面的session_state - OP 返回用户的登录状态(已登录 / 已登出)
session_state 参数由 OP 在授权响应中返回,客户端需要存储它以用于后续的状态检查。
OP 发现文档中的会话管理相关字段
整个检查过程通过浏览器端的 postMessage API 完成,实现简洁,适合需要实时感知用户登出状态的 SPA 应用。
登出机制¶
OIDC 定义了三种互补的登出机制,分别解决不同的场景。
RP-Initiated Logout¶
用户在 RP 端主动发起登出。RP 将用户浏览器重定向到 OP 的 end_session_endpoint:
| 参数 | 说明 |
|---|---|
id_token_hint |
可选。之前颁发的 ID Token,帮助 OP 识别用户会话 |
post_logout_redirect_uri |
可选。登出后重定向的 URL,必须预先注册 |
state |
可选。用于维护请求/响应的关联,防止 CSRF |
OP 展示确认页面后执行登出,然后重定向回 RP。
Front-Channel Logout¶
OP 通过 iframe 通知 RP 登出。OP 将 RP 的 frontchannel_logout_uri 嵌入 iframe 中加载,RP 收到请求后清除本地会话。
优点:实现简单,无需 RP 提供额外的服务端端点。 缺点:依赖浏览器 iframe 加载,可能被浏览器策略阻止;不可靠(iframe 可能不执行)。
Back-Channel Logout¶
OP 通过服务端直接通知 RP 登出。OP 向 RP 的 backchannel_logout_uri 发送 HTTP POST 请求,携带一个 Logout Token:
Logout Token 是一个签名的 JWT,包含以下声明:
| Claim | 说明 |
|---|---|
iss |
OP 的 Issuer URL |
sub |
登出用户的主键标识 |
aud |
RP 的 Client ID |
iat |
Logout Token 的颁发时间 |
jti |
JWT ID,唯一标识此 Token |
sid |
Session ID,标识被终止的会话 |
events |
必须包含 http://schemas.openid.net/event/backchannel-logout |
优点:可靠,直接服务端通信,不依赖浏览器。 缺点:实现复杂,RP 需要提供 HTTPS 端点并验证 Logout Token 签名。
三种 Logout 对比¶
| 维度 | RP-Initiated | Front-Channel | Back-Channel |
|---|---|---|---|
| 触发方 | RP(用户主动) | OP | OP |
| 通知方式 | 浏览器重定向 | iframe 加载 | 服务端 HTTP POST |
| 可靠性 | 中(依赖用户确认) | 低(iframe 可能被阻止) | 高(直接通信) |
| 实现复杂度 | 低 | 中 | 高 |
| 适用场景 | 用户主动登出 | 简单场景 | 高安全要求场景 |
生产环境建议
高安全要求的系统应优先实现 Back-Channel Logout,确保 OP 端登出后能可靠地通知所有 RP 清除本地会话。Front-Channel Logout 适合作为补充手段。