OpenID Connect (OIDC) 协议
OpenID Connect (OIDC) 是建立在 OAuth 2.1 协议之上的身份层。它允许客户端验证终端用户的身份,并获取关于用户的基本个人信息(如姓名、邮箱等)。
什么是 OIDC
OAuth 2.0/2.1 只解决授权问题——让第三方应用能访问用户的资源。但它不提供用户身份信息的标准化方式。OIDC 在 OAuth 之上增加了身份认证能力:
- OAuth 2.1:提供 Access Token,用于授权客户端访问受保护的资源(API)
- OIDC:额外提供 ID Token,用于向客户端证明用户的身份
简单来说:OAuth 告诉应用"这个用户允许你做什么",OIDC 告诉应用"这个用户是谁"。
ID Token 与 Access Token 的区别
| 特性 | ID Token | Access Token |
|---|---|---|
| 面向方 | 客户端应用 | 资源服务器 |
| 数据格式 | 必须是 JWT 格式 | 格式不限(通常为 JWT 或随机字符串) |
| 主要用途 | 验证用户身份、展示用户信息 | 用于请求受保护的资源数据 |
| 安全要求 | 客户端需校验签名与有效期 | 资源服务器需校验权限级别 |
这两种令牌经常一起返回,但用途不同。ID Token 是给客户端应用看的,Access Token 是给 API 看的。
OIDC 的核心组件
1. ID Token
ID Token 是一个标准的 JWT,包含用户身份信息(Claims):
{
"iss": "https://accounts.google.com",
"sub": "110169484474386276334",
"aud": "1087834940803.apps.googleusercontent.com",
"exp": 1353604800,
"iat": 1353601200,
"email": "[email protected]",
"email_verified": true,
"name": "张三",
"picture": "https://example.com/photo.jpg"
}
常用字段说明:
| 字段 | 含义 |
|---|---|
sub | 用户的唯一标识符(Subject) |
iss | 签发者标识(Issuer) |
aud | 接收方客户端 ID(Audience) |
exp | 过期时间 |
iat | 签发时间 |
email | 用户邮箱(需要 email scope) |
name | 用户姓名(需要 profile scope) |
2. UserInfo Endpoint
当 ID Token 中的信息不足以满足业务需求时,客户端可以使用 Access Token 请求 UserInfo 端点获取更详细的用户信息:
GET /userinfo HTTP/1.1
Host: accounts.google.com
Authorization: Bearer ya29.xxx
响应示例:
{
"sub": "110169484474386276334",
"name": "张三",
"given_name": "三",
"family_name": "张",
"email": "[email protected]",
"email_verified": true,
"picture": "https://lh3.googleusercontent.com/..."
}
3. Discovery Document
OIDC 提供商通常会在固定位置公开一个配置文件,客户端可以自动发现所有必要的端点地址:
https://accounts.google.com/.well-known/openid-configuration
这个 JSON 文件包含:
authorization_endpoint:授权端点地址token_endpoint:令牌端点地址userinfo_endpoint:用户信息端点地址jwks_uri:公钥地址(用于验证 ID Token 签名)scopes_supported:支持的 scoperesponse_types_supported:支持的响应类型
利用发现文档,客户端无需硬编码各个端点地址。
核心流程
OIDC 流程基于 OAuth 2.1 授权码模式,关键区别在于 scope 参数包含 openid:
1. 发起授权请求
https://accounts.google.com/o/oauth2/v2/auth?
response_type=code&
client_id=YOUR_CLIENT_ID&
redirect_uri=https://yourapp.com/callback&
scope=openid email profile&
state=random_state&
nonce=random_nonce
scope=openid:表示这是一个 OIDC 请求scope=email:请求获取用户邮箱scope=profile:请求获取用户姓名、头像等nonce:防止重放攻击的随机值
2. 获取授权码并交换令牌
用户同意授权后,授权服务器返回授权码。客户端用授权码换取令牌:
POST /token HTTP/1.1
Host: accounts.google.com
Content-Type: application/x-www-form-urlencoded
code=AUTHORIZATION_CODE&
client_id=YOUR_CLIENT_ID&
client_secret=YOUR_CLIENT_SECRET&
redirect_uri=https://yourapp.com/callback&
grant_type=authorization_code
响应包含两种令牌:
{
"access_token": "ya29.xxx",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "1//xxx",
"id_token": "eyJhbGciOiJSUzI1NiIs..."
}
3. 验证 ID Token
客户端必须验证 ID Token 的合法性:
- 验证签名:使用发现文档中的公钥验证 JWT 签名
- 验证 iss:确认签发者是预期的身份提供商
- 验证 aud:确认受众是当前客户端 ID
- 验证 exp:确认令牌未过期
- 验证 nonce:确认与请求时发送的 nonce 一致
代码实现
Node.js(Google Auth Library)
const { OAuth2Client } = require('google-auth-library');
const client = new OAuth2Client(CLIENT_ID);
async function verifyIdToken(idToken) {
try {
const ticket = await client.verifyIdToken({
idToken: idToken,
audience: CLIENT_ID,
});
const payload = ticket.getPayload();
// 获取用户信息
const userId = payload['sub'];
const email = payload['email'];
const name = payload['name'];
return { userId, email, name };
} catch (error) {
console.error('ID Token 验证失败:', error);
throw error;
}
}
Python(Authlib)
from authlib.integrations.starlette_client import OAuth
oauth = OAuth()
oauth.register(
name='google',
client_id='YOUR_CLIENT_ID',
client_secret='YOUR_CLIENT_SECRET',
server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
client_kwargs={'scope': 'openid email profile'}
)
@app.route('/login')
async def login(request):
redirect_uri = request.url_for('auth')
return await oauth.google.authorize_redirect(request, redirect_uri)
@app.route('/auth')
async def auth(request):
token = await oauth.google.authorize_access_token(request)
user = token.get('userinfo')
# user 包含 sub, email, name 等信息
return {'user': user}
Java(Spring Security)
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(userInfo -> userInfo
.oidcUserService(this.oidcUserService())
)
);
return http.build();
}
private OidcUserService oidcUserService() {
OidcUserService oidcUserService = new OidcUserService();
return oidcUserService;
}
}
常见 OIDC 提供商
| 提供商 | 发现文档地址 |
|---|---|
https://accounts.google.com/.well-known/openid-configuration | |
| Microsoft | https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration |
| GitHub | GitHub 使用 OAuth 2.0,非完全 OIDC |
| Auth0 | https://{domain}/.well-known/openid-configuration |
| Keycloak | https://{host}/realms/{realm}/.well-known/openid-configuration |
OIDC 的优势
- 标准化:不同提供商的用户信息字段通过 OIDC 实现规范化,降低了多平台集成成本
- 安全性:ID Token 经过签名,可以验证完整性和真实性
- 互操作性:支持跨域单点登录(SSO)和标准化会话管理
- 自动发现:通过发现文档可以快速对接标准身份提供商
选用建议
建议使用 OIDC 的场景:
- 第三方登录集成(Google、GitHub 登录)
- 企业内部单点登录(SSO)
- 需要标准化用户信息接口
不需要 OIDC 的场景:
- 仅涉及服务器间通信(Machine-to-Machine),使用 OAuth 客户端凭据模式即可
- 自研认证系统,不需要对接第三方提供商
小结
本章学习了 OpenID Connect 的核心概念:
- 定位:OIDC 是 OAuth 2.1 之上的身份认证层
- ID Token:包含用户身份信息的标准 JWT
- UserInfo:可获取更详细的用户信息
- Discovery:自动发现端点配置
- 流程:基于授权码模式,scope 包含 openid
练习
- 使用 Google OIDC 实现第三方登录
- 手动验证一个 ID Token 的签名和声明
- 实现 SSO 单点登录功能