中转登录开放平台 · 接入文档
站长免申请官网接口,自助接入 QQ 快捷登录:发起登录 → 换取信息 → 查询用户
0中转登录开放平台
中转登录 · 免申请官网接口
把 QQ 快捷登录能力,输出到你的网站
ZeroUserCenter 是一个中转登录开放平台:你(站长 / 第三方开发者)无需向 QQ 互联申请任何接口或资质审核,只需自助申请一对密钥(App ID / App Secret),按本文档在自己的网站里对接,即可让用户通过 QQ 一键登录你的站点,并安全地获取其 QQ 资料(昵称、头像、openid 等)。
- ✓ 自助申请密钥,秒级开通,零人工审核
- ✓ 一份文档搞定:发起登录 → 换取信息 → 查询用户
- ✓ 服务端签名校验 + 回调域名白名单,安全可控
- ✓ 对接身份复用 QQ 登录,无需另注册开发者账号
1快速开始
0
申请接入密钥(自助,秒级开通)
- 用 QQ 账号 登录本平台(登录即自动成为开发者,无需另注册开发者账号)。
- 进入 控制台 → 对接凭据,点击「申请接入密钥」。
- 系统立即下发一对
App ID/App Secret;App Secret仅你自己可见,可随时一键重置。 - (推荐)在「回调域名白名单」填入你网站的回调域名,防止 App Secret 泄漏后被冒用。
1
发起登录
用户点击 QQ 登录 → 重定向到本站 login.php(带签名)
2
授权回调
用户在 QQ 授权完成 → 本站回调地址接收授权 → 跳转回您的网站并附带 token
3
换取用户信息
您的服务器用 token 调用 exchange_token 接口 → 获取 QQ 用户信息(含 qq_openid)
4
随时查询(可选)
凭 qq_openid 调用 query_user 接口 → 获取用户最新资料,无需重新登录
2对接凭据
以下信息请登录后在 控制台 → 对接凭据 查看 / 重置。未登录?点此跳转 →
| 字段 | 说明 | 获取方式 |
|---|---|---|
App ID |
应用唯一标识,公开,可放在前端请求登录链接里 | 控制台 → 对接凭据 |
App Secret |
签名密钥,仅保存在服务器端,切勿泄露到前端。可随时在控制台一键重置 | 控制台 → 对接凭据(仅登录本人可见) |
本站实际站点地址 |
生成签名或登录跳转时必须使用您部署时的真实域名(协议 + 主机 + 可选端口) | 下方「在线调试」工具会自动根据当前页面地址生成;管理后台也可在站点配置中查看 |
回调域名白名单(安全建议):建议在控制台「对接凭据 → 回调域名白名单」中填入你网站的回调域名(如
your-site.com 或 .your-site.com 通配子域)。只有白名单内的域名才能作为 cross_site 回调地址;留空 = 不限制(向后兼容,但不推荐——App Secret 一旦泄漏,攻击者可借你的应用向任意站点骗取用户 token)。
动态传入但需授权:回调地址仍通过
cross_site 参数在每次请求里动态传入(无需固定登记某个 URL),但其域名必须命中你应用的白名单,否则请求会被拒绝(错误提示:回调域名未授权:xxx 不在应用允许域名内)。
完整链接提示:本页所有接口行右侧「
复制」按钮,复制的都是带当前部署域名的完整绝对链接,可直接粘贴到您的后端代码里。调试工具点击一次「生成签名」后,文档内三处接口地址也会被同步替换为完整地址。
3签名机制
所有需要鉴权的接口,必须同时传递以下参数并生成签名。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
app_id | string | 是 | 您的 App ID |
timestamp | int | 是 | 当前 Unix 时间戳(秒),允许 ±5 分钟偏差 |
nonce | string | 是 | 随机字符串(推荐 16-32 位),每次请求唯一,防重放 |
sign | string | 是 | HMAC-SHA256 签名(hex 小写,64 位) |
签名步骤
- 收集所有业务参数(
app_id、timestamp、nonce以及接口自身的其他参数,不含sign),过滤掉空值。 - 按参数名字典序排序。
- 拼成
key1=urlencode(value1)&key2=urlencode(value2)格式(rawurlencode)。 - 使用
App Secret作为 key,对拼接串做HMAC-SHA256,输出小写 hex。
签名示例(PHP)
// 1. 准备参数 $params = [ 'app_id' => 'a1b2c3d4e5f60789', 'cross_site' => 'https://your-site.com/callback', 'timestamp' => time(), 'nonce' => bin2hex(random_bytes(16)), ]; // 2. 过滤空值 + 字典序排序 $params = array_filter($params, fn($v) => $v !== '' && $v !== null); ksort($params); // 3. 拼接(rawurlencode) $parts = []; foreach ($params as $k => $v) { $parts[] = $k . '=' . rawurlencode((string)$v); } $signStr = implode('&', $parts); // 4. HMAC-SHA256 $appSecret = '你的AppSecret'; $sign = hash_hmac('sha256', $signStr, $appSecret); // 5. 最终请求携带 sign $params['sign'] = $sign;
签名示例(Python)
# 与 PHP 一致:值用 rawurlencode(RFC 3986,保留 -_.~) import hmac, hashlib, time, secrets, urllib.parse APP_ID = "a1b2c3d4e5f60789" APP_SECRET = "你的AppSecret" def sign(params, secret): p = {k: v for k, v in params.items() if v not in ("", None)} raw = "&".join( k + "=" + urllib.parse.quote(str(p[k]), safe="-_.~") for k in sorted(p) ) return hmac.new(secret.encode(), raw.encode(), hashlib.sha256).hexdigest()
签名示例(Node.js)
const crypto = require('crypto'); const APP_ID = 'a1b2c3d4e5f60789'; const APP_SECRET = '你的AppSecret'; function sign(params, secret) { const filtered = {}; for (const k in params) { if (params[k] !== '' && params[k] !== null) filtered[k] = params[k]; } const raw = Object.keys(filtered).sort() .map(k => k + '=' + encodeURIComponent(String(filtered[k]))).join('&'); return crypto.createHmac('sha256', secret).update(raw).digest('hex'); }
编码一致性:服务端使用 PHP
rawurlencode(RFC 3986,保留 -_.~)。Python 请用 urllib.parse.quote(v, safe="-_.~");Node 的 encodeURIComponent 在 !*'() 等极少数字符上略有差异,常规回调地址 / Token 不受影响。
4在线调试
在浏览器端本地生成签名与登录链接,App Secret 不会上传到服务器,仅用于本地计算签名。
说明:点击「生成签名」会在浏览器本地用 HMAC-SHA256 计算签名,并拼出完整的
login.php 跳转链接。点击链接会真实发起 QQ 授权流程,授权完成后会带 token 跳转到您填写的回调 URL。
5接口一:发起登录跳转
请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
cross_site | string | 跨站对接必填 | 您的回调 URL,登录成功后本站重定向到此地址并附带 token。传了才走跨站对接模式,否则按本站登录流程处理 |
redirect | string | 否 | 站内相对路径(如 app.php、docs.php)。仅本站登录场景有效(不传 cross_site 时),登录完成后跳回此页面 |
app_id | string | 跨站对接必填 | App ID |
timestamp | int | 跨站对接必填 | Unix 时间戳(秒),允许 ±5 分钟偏差 |
nonce | string | 跨站对接必填 | 随机串(推荐 16-32 位/次),同一 app_id 在签名有效期内不可重复 |
sign | string | 跨站对接必填 | HMAC 签名(参与签名参数为:app_id、cross_site、timestamp、nonce)。不传 cross_site 时不需要签名 |
回调返回(重定向到您的 cross_site)
https://your-site.com/callback?token=2f8e3b...&from=qq_login_api
重要:必须携带
cross_site 参数才会进入跨站对接模式,否则会按本站登录流程处理并默认回到 app.php。
提示:当文档中
/login.php、/api.php 以 / 开头时,表示它们相对于本站点实际部署域名的根路径;若本项目部署在子目录(如 /qq/),请在使用时自行在前面拼接子目录前缀。
6接口二:Token 交换用户信息
拿到
token 后,您的服务器端调用此接口换取 QQ 用户信息。请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
token | string | 是 | 从回调 URL 获取的登录令牌(有效期内一次性使用) |
app_id | string | 是 | App ID(必须与登录时使用的 app_id 一致) |
timestamp | int | 是 | Unix 时间戳(秒),允许 ±5 分钟偏差 |
nonce | string | 是 | 随机串(推荐 16-32 位/次),同一 app_id 在签名有效期内不可重复 |
sign | string | 是 | HMAC 签名(参与签名参数为:app_id、token、timestamp、nonce) |
响应示例
{
"code": 200,
"message": "Token验证成功",
"data": {
"is_login": true,
"user": {
"id": 128,
"nickname": "小明",
"avatar": "https://thirdqq.qlogo.cn/.../100",
"gender": 1,
"qq_openid": "A1B2C3D4E5F6..."
}
},
"timestamp": 1722585600
}
错误码
| code | 说明 |
|---|---|
200 | 成功 |
400 | 缺少 token 参数 |
401 | 缺少 app_id / app_id 无效 / 缺少 timestamp / 缺少 nonce / 签名已过期 / 请求已处理,请勿重复提交 / 签名校验失败 |
403 | 应用已被禁用 |
500 | 服务器异常(message 含错误详情) |
完整对接示例(服务端:Python)
import time, secrets, urllib.parse, urllib.request, json, hmac, hashlib APP_ID = "a1b2c3d4e5f60789" APP_SECRET = "你的AppSecret" BASE = "https://your-platform.com" # 换成你的部署域名 def sign(params): p = {k: v for k, v in params.items() if v not in ("", None)} raw = "&".join(k + "=" + urllib.parse.quote(str(p[k]), safe="-_.~") for k in sorted(p)) return hmac.new(APP_SECRET.encode(), raw.encode(), hashlib.sha256).hexdigest() # 1) 生成登录跳转链接,把用户 302 重定向到这里 login = { "app_id": APP_ID, "cross_site": "https://your-site.com/callback", "timestamp": int(time.time()), "nonce": secrets.token_hex(16), } login["sign"] = sign(login) login_url = BASE + "/login.php?" + urllib.parse.urlencode(login) # 2) 用户跳回 your-site.com/callback?token=xxx 后,服务器端换用户信息 def exchange(token): p = {"token": token, "app_id": APP_ID, "timestamp": int(time.time()), "nonce": secrets.token_hex(16)} p["sign"] = sign(p) url = BASE + "/api.php?action=exchange_token&" + urllib.parse.urlencode(p) with urllib.request.urlopen(url) as r: return json.loads(r.read().decode())
完整对接示例(服务端:Node.js)
const crypto = require('crypto'); const https = require('https'); const APP_ID = 'a1b2c3d4e5f60789'; const APP_SECRET = '你的AppSecret'; const BASE = 'https://your-platform.com'; // 换成你的部署域名 function sign(params) { const f = {}; for (const k in params) if (params[k] !== '' && params[k] !== null) f[k] = params[k]; const raw = Object.keys(f).sort() .map(k => k + '=' + encodeURIComponent(String(f[k]))).join('&'); return crypto.createHmac('sha256', APP_SECRET).update(raw).digest('hex'); } // 1) 生成登录跳转链接 function buildLoginUrl() { const p = { app_id: APP_ID, cross_site: 'https://your-site.com/callback', timestamp: Math.floor(Date.now() / 1000), nonce: crypto.randomBytes(16).toString('hex') }; p.sign = sign(p); return BASE + '/login.php?' + new URLSearchParams(p).toString(); } // 2) 回调拿到 token 后,服务器端换取用户信息 function exchange(token) { const p = { token, app_id: APP_ID, timestamp: Math.floor(Date.now() / 1000), nonce: crypto.randomBytes(16).toString('hex') }; p.sign = sign(p); const url = BASE + '/api.php?action=exchange_token&' + new URLSearchParams(p).toString(); return new Promise((resolve, reject) => { https.get(url, res => { let d = ''; res.on('data', c => d += c); res.on('end', () => resolve(JSON.parse(d))); }).on('error', reject); }); }
7接口三:查询用户信息
第三方在用户登录后任意时间,通过
qq_openid 主动查询用户最新信息。本接口不消耗 token,可反复调用。请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
qq_openid | string | 是 | 用户在本平台的唯一标识(登录时由 exchange_token 返回) |
app_id | string | 是 | App ID |
timestamp | int | 是 | Unix 时间戳(秒),允许 ±5 分钟偏差 |
nonce | string | 是 | 随机串(推荐 16-32 位/次),同一 app_id 在签名有效期内不可重复 |
sign | string | 是 | HMAC 签名(参与签名参数为:app_id、qq_openid、timestamp、nonce) |
响应示例
{
"code": 200,
"message": "查询成功",
"data": {
"user": {
"id": 128,
"nickname": "小明",
"avatar": "https://thirdqq.qlogo.cn/.../100",
"gender": 1,
"qq_openid": "A1B2C3D4E5F6...",
"last_login_time": "2026-08-02 10:23:45"
}
},
"timestamp": 1722585600
}
错误码
| code | 说明 |
|---|---|
200 | 查询成功 |
400 | 缺少 qq_openid 参数 |
401 | 缺少 app_id / app_id 无效 / 缺少 timestamp / 缺少 nonce / 签名已过期 / 请求已处理,请勿重复提交 / 签名校验失败 |
403 | 应用已被禁用 |
500 | 用户不存在或服务器异常(message 含错误详情) |
使用场景:用户首次登录后已拿到
qq_openid,后续需要获取最新昵称/头像(例如用户在 QQ 端修改了资料)时,可直接调用本接口刷新,无需让用户重新走登录流程。
8通用响应结构
所有
api.php 接口返回统一的 JSON 结构:{
"code": 200,
"message": "说明文本",
"data": { /* 业务数据 */ },
"timestamp": 1722585600
}
9辅助接口
以下 URL 全部相对于当前部署域名的站点根(即文档顶部三块「接口一/二/三」所在的同一根),下表的每一行右侧都提供了「复制完整链接」和「打开确认」按钮。
| 方法 | URL | 说明 | 快捷操作 |
|---|---|---|---|
| GET | /api.php |
接口总览:返回服务状态与核心端点列表(含签名接口、站点接口) | 打开 |
| GET | /api.php?action=site_config |
获取站点前端配置:站点名、标题、Logo、备案号、QQ App ID、QQ 回调地址、版本号、部署根路径 base_path(轻量缓存 60s) |
打开 |
| GET | /api.php?action=get_stats |
获取平台统计:注册用户数、应用数、API 调用总量 | 打开 |
| GET | /api.php?action=get_auth_url |
获取 QQ 授权 URL。可选参数:state(防 CSRF 自定义态);跨站请用 login.php 的 cross_site 参数 |
打开 |
| GET | /api.php?action=check_login |
检查本站 Cookie 会话登录状态(本站端对接使用,非第三方跨站接口) | 打开 |
| GET | /api.php?action=get_user_info |
获取本站会话下当前用户详情(需已登录)。若您需要第三方后台在任意时刻查用户,请使用 query_user | 打开 |
| POST | /api.php?action=logout |
退出本站会话(附带 Cookie/CSRF,仅本站端对接使用)。前端登出按钮通常同时跳转到 logout.php 完成最终清理 | 打开登出页 |
对接方说明:第三方网站对接 QQ 登录时,一般只需要 login.php(发起登录) + exchange_token(换用户信息) + query_user(后续刷新) 三个接口;其余「辅助接口」主要供本站前端页面使用。