OpenID Connect 和 OAuth 2.0
本文将讲解 Web 应用程序中 ID 管理和授权的标准协议。
什么是 OAuth 2.0
OAuth 2.0 是用于 授权 (Authorization) 的开放标准协议。
主要目的
- 允许用户授予第三方应用程序访问其资源(数据)的权限
- 在不共享密码的情况下,委派对特定资源的访问权限
OAuth 2.0 的四个主要角色
- 资源所有者 (Resource Owner): 资源的拥有者(通常是最终用户)
- 客户端 (Client): 想要访问资源的应用程序
- 授权服务器 (Authorization Server): 颁发访问令牌的服务器
- 资源服务器 (Resource Server): 提供受保护资源的服务器
OAuth 2.0 的主要流程
授权码模式 (Authorization Code Flow)
这是最安全且通常被推荐的流程。
特点:
- 不直接将机密信息(访问令牌)传递给前端
- 在后端进行令牌交换,因此安全性高
- SPA 和原生应用也可以结合 PKCE 扩展使用
授权码模式 + PKCE (Authorization Code Flow with PKCE)
PKCE (Proof Key for Code Exchange) 是 OAuth 2.0 的扩展规范,用于防止授权码拦截攻击的安全增强方法。
为什么需要 PKCE:
- SPA 和移动应用无法安全地存储 client_secret
- 授权码可能会被拦截
- 增强公共客户端(没有客户端密钥的客户端)的安全性
PKCE 的机制:
PKCE 的参数:
-
code_verifier:
- 43〜128 个字符的随机字符串
- 客户端在本地生成并保存
- 例如:
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
-
code_challenge:
- code_verifier 的 SHA256 哈希值的 Base64URL 编码
- 在授权请求时发送
- 例如:
E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
-
code_challenge_method:
S256(推荐 SHA256) 或plain(不推荐)
实现示例:
// 1. 生成 code_verifier
function generateCodeVerifier() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return base64URLEncode(array);
}
// 2. 生成 code_challenge
async function generateCodeChallenge(verifier) {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const hash = await crypto.subtle.digest('SHA-256', data);
return base64URLEncode(new Uint8Array(hash));
}
// Base64URL 编码
function base64URLEncode(buffer) {
return btoa(String.fromCharCode(...buffer))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}
// 3. 授权请求
async function startAuthFlow() {
const codeVerifier = generateCodeVerifier();
const codeChallenge = await generateCodeChallenge(codeVerifier);
// 将 code_verifier 保存到会话存储中
sessionStorage.setItem('code_verifier', codeVerifier);
const authUrl = new URL('https://auth.example.com/authorize');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('client_id', 'your-client-id');
authUrl.searchParams.set('redirect_uri', 'https://yourapp.com/callback');
authUrl.searchParams.set('scope', 'openid profile email');
authUrl.searchParams.set('code_challenge', codeChallenge);
authUrl.searchParams.set('code_challenge_method', 'S256');
authUrl.searchParams.set('state', generateRandomState());
window.location.href = authUrl.toString();
}
// 4. 回调处理 (获取令牌)
async function handleCallback() {
const params = new URLSearchParams(window.location.search);
const code = params.get('code');
const state = params.get('state');
// 验证 state
if (state !== sessionStorage.getItem('state')) {
throw new Error('Invalid state');
}
// 获取保存的 code_verifier
const codeVerifier = sessionStorage.getItem('code_verifier');
// 请求令牌端点
const tokenResponse = await fetch('https://auth.example.com/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
redirect_uri: 'https://yourapp.com/callback',
client_id: 'your-client-id',
code_verifier: codeVerifier, // PKCE 的关键参数
}),
});
const tokens = await tokenResponse.json();
// 清理 code_verifier
sessionStorage.removeItem('code_verifier');
sessionStorage.removeItem('state');
return tokens;
}
PKCE 的优点:
- ✅ 不需要 client_secret (最适合 SPA 和移动应用)
- ✅ 防止授权码拦截攻击
- ✅ 即使攻击者拦截了代码,如果没有 code_verifier 也无法获取令牌
- ✅ 所有 OAuth 2.0 客户端推荐的标准
安全要点:
- code_verifier 必须足够随机且不可预测
- code_verifier 仅在客户端保存,不发送(仅在令牌交换时)
- code_challenge 在授权服务器端安全保存
- code_challenge_method 使用
S256(plain不推荐)
隐式流程 (Implicit Flow)
※ 目前不推荐。SPA 应使用授权码模式 + PKCE。
客户端凭证模式 (Client Credentials Flow)
用于服务器间通信的流程。
客户端 → 授权服务器: client_id + client_secret
授权服务器 → 客户端: 访问令牌
客户端 → 资源服务器: 使用访问令牌访问 API
用途:
- 微服务间通信
- 批处理
- 不需要用户上下文的 API 调用
什么是 OpenID Connect (OIDC)
OpenID Connect 是构建在 OAuth 2.0 之上的 认证 (Authentication) 层。
与 OAuth 2.0 的区别
| 项目 | OAuth 2.0 | OpenID Connect |
|---|---|---|
| 主要目的 | 授权 (Authorization) | 认证 (Authentication) |
| 获取的信息 | 访问权限 | 用户 ID 信息 |
| 令牌 | Access Token | ID Token + Access Token |
| 用例 | API 访问权限委派 | 登录・SSO |
ID Token
OpenID Connect 的核心概念是 ID Token。
- JWT (JSON Web Token) 格式
- 包含用户的认证信息
- 已签名,可验证是否被篡改
ID Token 结构示例:
{
"iss": "https://auth.example.com",
"sub": "user123",
"aud": "client-app-id",
"exp": 1735689600,
"iat": 1735686000,
"name": "山田太郎",
"email": "yamada@example.com",
"email_verified": true
}
主要声明 (Claims):
iss: 令牌颁发者sub: 用户唯一标识符aud: 令牌的目标客户端exp: 有效期iat: 签发时间
OpenID Connect 流程
基本上与 OAuth 2.0 的授权码模式相同,但有以下区别:
-
Scope 中包含
openidscope=openid profile email -
颁发 ID Token
- 除了 Access Token 外,还获取 ID Token
- ID Token 中包含用户信息
-
UserInfo 端点
- 可获取更详细的用户信息
GET /userinfoAuthorization: Bearer {access_token}
实现示例
客户端的授权请求
// 重定向到授权端点
const authUrl = new URL('https://auth.example.com/authorize');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('client_id', 'your-client-id');
authUrl.searchParams.set('redirect_uri', 'https://yourapp.com/callback');
authUrl.searchParams.set('scope', 'openid profile email');
authUrl.searchParams.set('state', generateRandomState()); // CSRF 对策
window.location.href = authUrl.toString();
后端获取令牌
// 在回调中接收授权码
app.get('/callback', async (req, res) => {
const { code, state } = req.query;
// 验证 state (CSRF 对策)
if (state !== req.session.state) {
return res.status(400).send('Invalid state');
}
// 请求令牌端点
const tokenResponse = await fetch('https://auth.example.com/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
redirect_uri: 'https://yourapp.com/callback',
client_id: 'your-client-id',
client_secret: 'your-client-secret',
}),
});
const tokens = await tokenResponse.json();
// tokens.access_token, tokens.id_token, tokens.refresh_token
// 验证 ID Token
const idToken = verifyJWT(tokens.id_token);
// 保存到会话
req.session.userId = idToken.sub;
req.session.accessToken = tokens.access_token;
res.redirect('/dashboard');
});
安全注意事项
必须的措施
-
使用 HTTPS
- 所有通信必须通过 HTTPS 进行
-
State 参数
- 使用 state 参数进行验证以防止 CSRF 攻击
-
PKCE (Proof Key for Code Exchange)
- SPA 和移动应用必须使用
- 防止授权码拦截攻击
-
安全存储令牌
- 妥善保护 Access Token
- 不要保存在浏览器的 LocalStorage 中 (XSS 对策)
-
最小化 Scope
- 仅请求必要的最小 Scope
-
令牌有效期
- 为 Access Token 设置较短的有效期
- 实现使用 Refresh Token 更新的机制
主要提供商
- Azure Active Directory (Microsoft Entra ID)
- Auth0
- Okta
- Google Identity Platform
- Amazon Cognito
- Keycloak (开源)
总结
- OAuth 2.0: API 访问授权框架
- OpenID Connect: 基于 OAuth 2.0 的认证协议
- 授权码模式: 最安全且推荐的流程
- ID Token: 包含用户认证信息的 JWT
- 安全: 必须采取 HTTPS、State、PKCE 等措施