OAuth 2.0 开发文档
黑与白统一通行证系统 统一通行证 OAuth 2.0 接入指南,帮助您快速集成第三方登录。
概述
OAuth 2.0 是一个行业标准的授权协议,允许第三方应用在用户授权的前提下,安全地访问用户在 黑与白统一通行证系统 上的数据,而无需获取用户的密码。
支持的授权流程
目前支持 授权码模式(Authorization Code),这是最安全、最常用的 OAuth 2.0 授权流程,适用于有后端服务器的 Web 应用。
使用 OAuth 2.0 的优势
- 用户无需向第三方应用提供密码
- 可精确控制第三方应用的访问权限范围
- 用户可随时撤销对第三方应用的授权
- 令牌有过期时间,降低安全风险
- 支持刷新令牌,无需用户反复授权
快速开始
OAuth 2.0 授权码流程分为以下四个步骤:
重定向授权
将用户重定向到授权端点,携带 client_id、redirect_uri 等参数。
用户授权
用户在授权页面登录并确认授权,系统携带授权码重定向回您的应用。
换取令牌
您的服务端使用授权码向令牌端点换取 access_token 和 refresh_token。
获取用户信息
使用 access_token 调用用户信息端点,获取用户数据。
接口端点
3.1 授权端点
/oauth/authorize将用户浏览器重定向到此端点以发起授权流程。
请求参数
| 参数 | 必填 | 说明 |
|---|---|---|
client_id | 必填 | 您的应用客户端 ID |
redirect_uri | 必填 | 授权完成后的回调地址 |
response_type | 必填 | 固定值 code |
scope | 可选 | 请求的权限范围,多个用空格分隔 |
state | 推荐 | 随机字符串,用于防止 CSRF 攻击 |
示例重定向 URL
/oauth/authorize?client_id=your_client_id&redirect_uri=https://your-app.com/callback&response_type=code&scope=read:user read:email&state=random_state_string可用的权限范围(Scope)
3.2 令牌端点
/oauth/token授权码换取令牌
使用授权码换取访问令牌和刷新令牌。
| 参数 | 说明 |
|---|---|
grant_type | 固定值 authorization_code |
code | 授权端点返回的授权码 |
redirect_uri | 与授权请求中一致的回调地址 |
client_id | 您的应用客户端 ID |
client_secret | 您的应用客户端密钥 |
请求示例
curl -X POST /oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=AUTH_CODE" \
-d "redirect_uri=https://your-app.com/callback" \
-d "client_id=your_client_id" \
-d "client_secret=your_client_secret"成功响应
{
"access_token": "a1b2c3d4...",
"refresh_token": "e5f6g7h8...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read:user read:email"
}刷新令牌
当访问令牌过期时,使用刷新令牌获取新的访问令牌,无需用户重新授权。
| 参数 | 说明 |
|---|---|
grant_type | 固定值 refresh_token |
refresh_token | 之前获取的刷新令牌 |
client_id | 您的应用客户端 ID |
client_secret | 您的应用客户端密钥 |
请求示例
curl -X POST /oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token=your_refresh_token" \
-d "client_id=your_client_id" \
-d "client_secret=your_client_secret"3.3 用户信息端点
/oauth/userinfo使用访问令牌获取已授权用户的信息。返回的字段取决于授权时请求的 scope。
请求示例
curl -H "Authorization: Bearer your_access_token" \
/oauth/userinfo完整响应示例
{
"sub": "1",
"id": "1",
"name": "张三",
"preferred_username": "张三",
"email": "[email protected]",
"email_verified": true,
"qq_number": "123456789",
"avatar": "https://q2.qlogo.cn/headimg_dl?dst_uin=123456789&spec=5",
"avatar_type": "qq",
"level": 0,
"level_name": "未认证",
"level_color": "#999999",
"level_icon": "👤",
"created_at": "2025-01-01 10:00:00",
"updated_at": "2025-01-15 14:30:00"
}3.4 配置发现端点
/api/oauth/endpoint返回 OpenID Connect 兼容的服务端配置信息,可用于自动发现各端点地址。
响应示例
{
"issuer": "",
"authorization_endpoint": "/oauth/authorize",
"token_endpoint": "/oauth/token",
"userinfo_endpoint": "/oauth/userinfo",
"scopes_supported": [
"read:profile",
"read:email",
"read:user",
"read:qq",
"read:avatar",
"read:level",
"write:profile"
],
"response_types_supported": ["code"],
"grant_types_supported": [
"authorization_code",
"refresh_token"
],
"token_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post"
]
}权限范围
通过指定不同的 scope,您可以精确控制应用访问的数据范围。
| Scope | 说明 | 返回字段 |
|---|---|---|
read:profile | 基本资料 | name, preferred_username, updated_at, level 信息, created_at |
read:email | 邮箱信息 | email, email_verified |
read:user | 用户信息 | name, preferred_username |
read:qq | QQ 号 | qq_number |
read:avatar | 用户头像 | avatar, avatar_type |
read:level | 等级信息 | level, level_name, level_color, level_icon |
write:profile | 修改资料 | (写入权限) |
完整集成示例
以下是一个使用 Node.js + Express 接入 OAuth 2.0 的完整示例,演示了从发起授权到获取用户信息的全流程。
const express = require('express');
const app = express();
const CLIENT_ID = 'your_client_id';
const CLIENT_SECRET = 'your_client_secret';
const REDIRECT_URI = 'http://localhost:3000/callback';
const AUTH_URL = '';
// 第一步:重定向到授权页面
app.get('/login', (req, res) => {
const params = new URLSearchParams({
client_id: CLIENT_ID,
redirect_uri: REDIRECT_URI,
response_type: 'code',
scope: 'read:user read:email read:avatar',
state: crypto.randomUUID(),
});
res.redirect(`${AUTH_URL}/oauth/authorize?${params}`);
});
// 第二步:处理授权回调
app.get('/callback', async (req, res) => {
const { code, state } = req.query;
// 使用授权码换取令牌
const tokenRes = await fetch(`${AUTH_URL}/oauth/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: REDIRECT_URI,
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
}),
});
const tokens = await tokenRes.json();
// 获取用户信息
const userRes = await fetch(`${AUTH_URL}/oauth/userinfo`, {
headers: { Authorization: `Bearer ${tokens.access_token}` },
});
const user = await userRes.json();
res.json({ user, tokens });
});
app.listen(3000);客户端凭证
令牌端点支持两种客户端凭证传递方式:
方式一:HTTP Basic 认证
将 client_id 和 client_secret 通过 Base64 编码后放在 Authorization 请求头中。
Authorization: Basic base64(client_id:client_secret)方式二:POST Body 参数
将 client_id 和 client_secret 作为表单参数直接放在请求体中。
client_id=your_client_id&client_secret=your_client_secret安全建议
始终使用 HTTPS
所有 OAuth 通信必须通过 HTTPS 进行,防止令牌在传输过程中被窃取。
使用 state 参数防止 CSRF
在授权请求中携带随机生成的 state 参数,并在回调时验证其一致性,防止跨站请求伪造攻击。
安全存储令牌
对于敏感应用,避免将令牌存储在 localStorage 中。推荐使用 HttpOnly Cookie 或服务端会话存储。
使用短期访问令牌 + 刷新令牌
访问令牌设置较短的过期时间,通过刷新令牌来续期,降低令牌泄露的风险。
服务端验证 redirect_uri
始终在服务端严格验证 redirect_uri 是否与预注册的回调地址一致,防止授权码被劫持。
错误处理
当请求出错时,API 会返回标准的 OAuth 2.0 错误响应。以下是常见的错误代码:
| 错误代码 | HTTP 状态码 | 说明 |
|---|---|---|
invalid_request | 400 | 请求缺少必要参数,或参数值无效 |
invalid_client | 401 | 客户端认证失败(client_id 或 client_secret 错误) |
invalid_grant | 400 | 授权码或刷新令牌无效、已过期或已被使用 |
unauthorized_client | 403 | 该客户端无权使用此授权类型 |
unsupported_grant_type | 400 | 不支持的授权类型(grant_type 值无效) |
invalid_scope | 400 | 请求的 scope 无效或超出范围 |
错误响应示例
{
"error": "invalid_grant",
"error_description": "授权码已过期或无效"
}