1 协议规则
| 请求方式 | GET |
| 传输协议 | HTTPS(推荐)/ HTTP |
| 数据格式 | JSON |
| 字符编码 | UTF-8 |
| 接口根地址 | https://connect.boshou.me/connect |
所有接口都必须由你的服务器调用。appkey 是签名密钥,一旦放到前端就等于公开。
2 接入流程
你的服务器 ──① 请求 /connect/login──▶ 本站
│ 本站向上游取授权地址
▼
你的网站 ◀── 返回 url ─────────────────┘
│ ② 把用户浏览器跳到 url
▼
第三方(QQ / 微信 …)──③ 用户授权完成 ──▶ 跳回你配置的 redirect_uri?type=qq&code=xxxx
│
▼
你的服务器 ──④ 用 code 请求 /connect/callback──▶ 本站
▼
返回 social_uid / nickname / faceimg …
拿到 social_uid 后,在你的用户表里按它查找或创建用户即可完成登录。
3 签名规则
- 除 sign 外的所有参数,按参数名 ASCII 升序排列
- 拼成 k1=v1&k2=v2&…(原始值,不做 urlencode)
- 末尾追加 &key={appkey}
- 对整串取 sha256,得到小写十六进制即为 sign
- 请求必须携带 timestamp,与服务端相差超过 300 秒会被拒绝
服务端用 hash_equals 做恒定时间比对,你那边排序时注意大小写与数字的顺序(ASCII 序)。
4 获取授权地址
GET https://connect.boshou.me/connect/login
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| appid | string | 是 | 你的应用 ID |
| type | string | 是 | 登录方式,见下方对照表 |
| redirect_uri | string | 是 | 登录完成后跳回的地址,必须命中应用配置的回调域名白名单 |
| timestamp | int | 是 | 秒级时间戳,与服务端相差超过 300 秒会被拒绝 |
| sign | string | 是 | 请求签名,算法见「签名规则」 |
RESPONSE{
"code": 0,
"msg": "succ",
"type": "qq",
"url": "https://graph.qq.com/oauth2.0/show?...",
"qrcode": "https://…"
}
qrcode 仅微信和支付宝返回,是扫码登录地址,可用于 PC 端扫码。
5 换取用户信息
GET https://connect.boshou.me/connect/callback
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| appid | string | 是 | 你的应用 ID |
| type | string | 是 | 登录方式,需与获取授权地址时一致 |
| code | string | 是 | 第三方跳回你网站时带上的授权码 |
| timestamp | int | 是 | 秒级时间戳 |
| sign | string | 是 | 请求签名 |
授权码是一次性的,有效期 5 分钟,用完即失效。同一个 code 重复请求会返回 1008。
RESPONSE{
"code": 0,
"msg": "succ",
"type": "qq",
"social_uid": "AD3F5033279C8187CBCBB29235D5F827",
"access_token": "89DC9691E274D6B596FFCB8D43368234",
"faceimg": "https://thirdqq.qlogo.cn/g?b=oidb&k=…",
"nickname": "大白",
"location": "成都市",
"gender": "男",
"ip": "1.12.3.40"
}
| 字段 | 类型 | 说明 |
|---|---|---|
| social_uid | string | 第三方账号唯一标识 —— 用它来识别用户,也是额度计数的依据 |
| nickname | string | 用户昵称 |
| faceimg | string | 用户头像地址 |
| access_token | string | 第三方返回的令牌 |
| gender | string | 性别 |
| location | string | 所在地(仅支付宝 / 微信返回) |
| ip | string | 用户登录 IP |
6 复查用户信息
用户登录之后的任意时间,都可以用 social_uid 再次查询其信息(例如昵称、头像有更新时)。
GET https://connect.boshou.me/connect/query?appid=…&type=qq&social_uid=…×tamp=…&sign=…返回字段与 /callback 完全一致。
7 错误码
| code | 含义 | 说明 |
|---|---|---|
| 0 | 成功 | ok |
| 2 | 未完成登录 | 用户还没在第三方完成授权,稍后重试即可 |
| 1001 | 参数缺失 | 必填参数没传或为空 |
| 1002 | 签名错误 | sign 校验不通过,检查参数排序与 appkey |
| 1003 | 时间戳过期 | 与服务端时间相差超过 300 秒,校准服务器时间 |
| 1004 | 应用不存在 | appid 错误,或应用已停用 / 已删除 |
| 1005 | 套餐已过期 | 续费后即可恢复 |
| 1006 | 登录方式不可用 | 该登录方式不在当前套餐允许范围内 |
| 1007 | 账号额度已用尽 | 新账号无法继续登录,续费或升级套餐 |
| 1008 | 授权码无效 | code 已过期或已被使用(授权码一次性) |
| 1009 | 上游暂不可用 | 第三方服务异常,稍后重试 |
| 1010 | 请求过于频繁 | 触发接口限流,降低频率后重试 |
8 示例代码
$value) {
$parts[] = $key . '=' . $value;
}
return hash('sha256', implode('&', $parts) . '&key=' . APPKEY);
}
/** 带上签名发起请求 */
function api(string $action, array $params): array
{
$params['appid'] = APPID;
$params['timestamp'] = time();
$params['sign'] = sign($params);
$url = API_BASE . '/' . $action . '?' . http_build_query($params);
$raw = file_get_contents($url, false, stream_context_create([
'http' => ['timeout' => 10],
]));
return json_decode((string) $raw, true) ?: ['code' => -1, 'msg' => '接口无响应'];
}
// ───────── 第一步:拿到第三方授权地址,把用户送过去 ─────────
function startLogin(string $type, string $redirectUri): void
{
$res = api('login', [
'type' => $type,
'redirect_uri' => $redirectUri,
]);
if ((int) $res['code'] !== 0) {
exit('获取登录地址失败:' . $res['msg']);
}
// 微信 / 支付宝还会返回 qrcode(扫码地址),可用于 PC 扫码登录
header('Location: ' . $res['url']);
exit;
}
// ───────── 第二步:用户从第三方跳回来,用 code 换用户信息 ─────────
function handleCallback(string $type, string $code): array
{
$res = api('callback', ['type' => $type, 'code' => $code]);
if ((int) $res['code'] === 2) {
// 用户还没完成授权(比如扫码扫到一半),稍后重试即可
exit('正在登录中,请稍候刷新…');
}
if ((int) $res['code'] !== 0) {
exit('登录失败:' . $res['msg']);
}
// social_uid 是第三方账号的唯一标识,用它在你自己的库里找/建用户
return [
'social_uid' => $res['social_uid'],
'nickname' => $res['nickname'],
'avatar' => $res['faceimg'],
'gender' => $res['gender'],
];
}
// ───────── 第三步(可选):事后复查用户信息 ─────────
function queryUser(string $type, string $socialUid): array
{
return api('query', ['type' => $type, 'social_uid' => $socialUid]);
}
appid 已经帮你填好了;appkey 是占位符,请到「我的应用」里复制。
/**
* 聚合登录接入示例(Node.js)
*
* 必须跑在你的服务端。appkey 是签名密钥,不要下发到浏览器。
*/
const crypto = require('crypto');
const APPID = '你的 appid';
const APPKEY = '你的 appkey';
const API_BASE = 'https://connect.boshou.me/connect';
/** 签名:除 sign 外按参数名升序拼接,末尾追加 &key=appkey,取 sha256 */
function sign(params) {
const parts = Object.keys(params)
.filter((k) => k !== 'sign')
.sort()
.map((k) => `${k}=${params[k]}`);
return crypto.createHash('sha256')
.update(parts.join('&') + '&key=' + APPKEY)
.digest('hex');
}
/** 带上签名发起请求 */
async function api(action, params) {
const all = { ...params, appid: APPID, timestamp: Math.floor(Date.now() / 1000) };
all.sign = sign(all);
const url = `${API_BASE}/${action}?` + new URLSearchParams(all).toString();
const res = await fetch(url);
return res.json();
}
// 第一步:拿到第三方授权地址,重定向用户
async function startLogin(type, redirectUri) {
const res = await api('login', { type, redirect_uri: redirectUri });
if (res.code !== 0) {
throw new Error('获取登录地址失败:' + res.msg);
}
// 微信 / 支付宝还会有 res.qrcode(扫码地址)
return res.url;
}
// 第二步:用户跳回来后,用 code 换用户信息
async function handleCallback(type, code) {
const res = await api('callback', { type, code });
if (res.code === 2) {
throw new Error('用户尚未完成授权,请稍后重试');
}
if (res.code !== 0) {
throw new Error('登录失败:' + res.msg);
}
return {
socialUid: res.social_uid,
nickname: res.nickname,
avatar: res.faceimg,
};
}
// 第三步(可选):事后复查
function queryUser(type, socialUid) {
return api('query', { type, social_uid: socialUid });
}
9 常见问题
返回 1002 签名错误怎么办?
先确认参数按参数名 ASCII 升序排列、值没有做 urlencode、末尾是 &key=你的appkey。注意排序要区分大小写:大写字母排在小写之前。
返回 1003 时间戳过期?
服务器时间不准。校准一下系统时间(Linux 下可执行 ntpdate 或用 systemd-timesyncd),允许的偏差是 ±300 秒。
用户扫了码但一直停在「正在登录中」?
说明用户还没在第三方完成授权,接口会返回 code=2。这在扫码登录场景很正常,隔一两秒重试即可,不要当失败处理。
同一个用户重复登录会重复占用额度吗?
不会。额度按「不同的第三方账号」计数,同一个 QQ 号反复登录只算一个账号,而且在你名下的多个应用之间也是共用的。
额度用完了老用户还能登录吗?
可以。额度用尽只拦截「新账号」,已经登录过的用户不受影响,续费或升级后新账号立刻恢复。
redirect_uri 必须命中白名单吗?
必须。应用配置里的回调域名白名单是防止授权码被劫持的关键,只填主域名即可,子域名会自动放行。