OAuth 2.0 开发文档

黑与白统一通行证系统 统一通行证 OAuth 2.0 接入指南,帮助您快速集成第三方登录。

概述

OAuth 2.0 是一个行业标准的授权协议,允许第三方应用在用户授权的前提下,安全地访问用户在 黑与白统一通行证系统 上的数据,而无需获取用户的密码。

支持的授权流程

目前支持 授权码模式(Authorization Code),这是最安全、最常用的 OAuth 2.0 授权流程,适用于有后端服务器的 Web 应用。

使用 OAuth 2.0 的优势

  • 用户无需向第三方应用提供密码
  • 可精确控制第三方应用的访问权限范围
  • 用户可随时撤销对第三方应用的授权
  • 令牌有过期时间,降低安全风险
  • 支持刷新令牌,无需用户反复授权

快速开始

OAuth 2.0 授权码流程分为以下四个步骤:

1

重定向授权

将用户重定向到授权端点,携带 client_id、redirect_uri 等参数。

2

用户授权

用户在授权页面登录并确认授权,系统携带授权码重定向回您的应用。

3

换取令牌

您的服务端使用授权码向令牌端点换取 access_token 和 refresh_token。

4

获取用户信息

使用 access_token 调用用户信息端点,获取用户数据。

接口端点

3.1 授权端点

GET/oauth/authorize

将用户浏览器重定向到此端点以发起授权流程。

请求参数

参数必填说明
client_id必填您的应用客户端 ID
redirect_uri必填授权完成后的回调地址
response_type必填固定值 code
scope可选请求的权限范围,多个用空格分隔
state推荐随机字符串,用于防止 CSRF 攻击

示例重定向 URL

示例 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)

read:profileread:emailread:userread:qqread:avatarread:levelwrite:profile

3.2 令牌端点

POST/oauth/token

授权码换取令牌

使用授权码换取访问令牌和刷新令牌。

参数说明
grant_type固定值 authorization_code
code授权端点返回的授权码
redirect_uri与授权请求中一致的回调地址
client_id您的应用客户端 ID
client_secret您的应用客户端密钥

请求示例

授权码换取令牌
bash
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您的应用客户端密钥

请求示例

刷新令牌
bash
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 用户信息端点

GET/oauth/userinfo

使用访问令牌获取已授权用户的信息。返回的字段取决于授权时请求的 scope。

请求示例

获取用户信息
bash
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 配置发现端点

GET/api/oauth/endpoint

返回 OpenID Connect 兼容的服务端配置信息,可用于自动发现各端点地址。

响应示例

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:qqQQ 号qq_number
read:avatar用户头像avatar, avatar_type
read:level等级信息level, level_name, level_color, level_icon
write:profile修改资料(写入权限)

完整集成示例

以下是一个使用 Node.js + Express 接入 OAuth 2.0 的完整示例,演示了从发起授权到获取用户信息的全流程。

Node.js / Express 完整示例
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 请求头中。

http
Authorization: Basic base64(client_id:client_secret)

方式二:POST Body 参数

将 client_id 和 client_secret 作为表单参数直接放在请求体中。

http
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_request400请求缺少必要参数,或参数值无效
invalid_client401客户端认证失败(client_id 或 client_secret 错误)
invalid_grant400授权码或刷新令牌无效、已过期或已被使用
unauthorized_client403该客户端无权使用此授权类型
unsupported_grant_type400不支持的授权类型(grant_type 值无效)
invalid_scope400请求的 scope 无效或超出范围

错误响应示例

错误响应
{
  "error": "invalid_grant",
  "error_description": "授权码已过期或无效"
}