谈谈开放平台扫码登录的接入设计
2026年05月18日3 次阅读0 人喜欢
API微信小程序NestJSOAuth架构设计
最近把 api.nnnnzs.cn 的后端从 Express 迁移到 NestJS,顺便把扫码登录这套功能整理了一下,做成一个对外开放的平台。
之前这套扫码登录就自己用,现在想开放给第三方接入。所以涉及两个问题:怎么让第三方安全地用,同时不影响老逻辑。
整体流程
先说下扫码登录的流程,其实挺简单的:
sequenceDiagram
participant 第三方网站
participant 开放平台
participant 小程序
participant 微信
第三方网站->>开放平台: POST /open-platform/qr/getToken
开放平台-->>第三方网站: 返回 token
第三方网站->>开放平台: GET /open-platform/qr/getImg?token=xxx
开放平台-->>第三方网站: 返回二维码图片
第三方网站->>用户: 展示二维码
用户->>微信: 扫描二维码
微信->>小程序: 打开小程序(携带 token)
小程序->>开放平台: GET /auth/info?token=xxx
小程序->>开放平台: POST /auth/confirm(用户确认授权)
开放平台->>第三方网站: POST 回调通知(异步)
第三方网站-->>用户: 登录成功
核心就是:第三方创建 token → 生成二维码 → 用户扫码 → 小程序确认 → 平台回调通知第三方。
接入步骤
1. 注册应用
调用注册接口拿到 appKey 和 appSecret:
bash
POST https://api.nnnnzs.cn/open-platform/register
Content-Type: application/json
{
"appName": "我的应用",
"callbackUrl": "https://example.com/api/wechat/callback",
"description": "用于网站扫码登录",
"contactEmail": "dev@example.com"
}
返回:
json
{
"status": true,
"data": {
"appKey": "opk_xxxxxxxxxxxx",
"appSecret": "ops_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
}
appSecret只在注册时返回一次,请妥善保管。如果丢了可以调用重新生成接口。
2. 签名机制
大部分接口都需要签名鉴权,签名规则如下:
请求头:
| Header | 说明 |
|---|---|
X-App-Key |
你的 appKey |
X-Timestamp |
当前时间戳(秒) |
X-Signature |
签名值 |
签名算法:
签名字符串 = HTTP方法 + "\n" + 请求路径 + "\n" + 时间戳 + "\n" + 请求体JSON
签名值 = HMAC-SHA256(签名字符串, appSecret)
注意:请求体为空时,签名字符串最后一行为空字符串。时间戳有效期 5 分钟。
示例代码(Node.js):
javascript
const crypto = require('crypto');
function generateSignature(method, path, timestamp, body, appSecret) {
const bodyStr = body ? JSON.stringify(body) : '';
const signString = `${method}\n${path}\n${timestamp}\n${bodyStr}`;
return crypto
.createHmac('sha256', appSecret)
.update(signString)
.digest('hex');
}
// 用法
const timestamp = Math.floor(Date.now() / 1000).toString();
const signature = generateSignature('POST', '/open-platform/qr/getToken', timestamp, null, 'ops_your_app_secret');
fetch('https://api.nnnnzs.cn/open-platform/qr/getToken', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-App-Key': 'opk_your_app_key',
'X-Timestamp': timestamp,
'X-Signature': signature,
},
body: JSON.stringify({}),
});
3. 扫码登录接口
创建扫码会话
bash
POST /open-platform/qr/getToken
请求体可选,支持传入自定义参数:
json
{
"params": { "redirectUrl": "/dashboard" }
}
返回 token 和有效期:
json
{
"status": true,
"data": {
"token": "A1B2C3D4E5F6...",
"expiresIn": 3600
}
}
获取二维码图片
bash
GET /open-platform/qr/getImg?token=A1B2C3D4E5F6...&env_version=release
env_version 参数说明:
| 值 | 说明 |
|---|---|
release |
正式版 |
trial |
体验版(默认) |
develop |
开发版 |
返回的是 PNG 图片,直接用 <img> 标签展示就行。
查询扫码状态
bash
GET /open-platform/qr/status?token=A1B2C3D4E5F6...
返回:
json
{
"status": true,
"data": {
"token": "A1B2C3D4E5F6...",
"scanStatus": 0,
"openId": "oXXXXXXXX",
"scanData": {
"nickName": "用户昵称",
"avatarUrl": "https://..."
},
"params": { "redirectUrl": "/dashboard" },
"message": "已扫码,等待确认"
}
}
scanStatus 状态码:
| 值 | 说明 |
|---|---|
-1 |
等待扫码 |
0 |
已扫码,等待确认 |
1 |
扫码完成 |
4. 接收回调
用户在小程序上确认授权后,平台会向注册时填写的 callbackUrl 发送 POST 请求。
回调请求头:
| Header | 说明 |
|---|---|
X-App-Key |
你的 appKey |
X-Signature |
回调签名(用 appSecret 对请求体做 HMAC-SHA256) |
回调请求体:
json
{
"token": "A1B2C3D4E5F6...",
"appKey": "opk_xxxxxxxxxxxx",
"status": 1,
"openId": "oXXXXXXXX",
"scanData": {
"nickName": "用户昵称",
"avatarUrl": "https://thirdwx.qlogo.cn/..."
},
"params": { "redirectUrl": "/dashboard" },
"scannedAt": "2025-05-18T10:30:00.000Z",
"timestamp": 1734525000
}
回调会重试 3 次(间隔 1s、2s、4s),你的接口返回 2xx 就算成功。
验证回调签名:
javascript
const crypto = require('crypto');
function verifyCallback(bodyStr, appSecret, signature) {
const expected = crypto
.createHmac('sha256', appSecret)
.update(bodyStr)
.digest('hex');
return expected === signature;
}
前端集成示例
一个简单的前端页面,展示二维码并轮询状态:
javascript
async function startQrLogin() {
// 1. 创建会话(签名逻辑见上方)
const { data: { token } } = await createSession();
// 2. 展示二维码
const img = document.getElementById('qrcode');
img.src = `https://api.nnnnzs.cn/open-platform/qr/getImg?token=${token}&env_version=release`;
// 3. 轮询状态
const timer = setInterval(async () => {
const { data } = await checkStatus(token);
if (data.scanStatus === 1) {
clearInterval(timer);
// 登录成功,拿到 openId
console.log('登录成功:', data.openId, data.scanData);
}
}, 2000);
}
注意事项
- token 有效期 1 小时,过期需要重新创建
- 签名时间戳有效期 5 分钟
- 回调只会发送一次成功通知,重试最多 3 次
appSecret务必保密,不要暴露在前端代码里- 如果
appSecret泄露,调用重新生成接口即可
应用管理接口
注册之后还有一些管理接口可以查信息、更新配置:
| 接口 | 方法 | 说明 |
|---|---|---|
/open-platform/app/info |
POST | 查看应用信息 |
/open-platform/app/update |
POST | 更新应用信息(名称、回调地址等) |
/open-platform/app/regenerate-secret |
POST | 重新生成 appSecret |
这些接口同样需要签名鉴权。
Demo 页面
如果想快速体验,可以访问 Demo 页面(无需签名):
https://api.nnnnzs.cn/open-platform/demo/
这里有一个完整的扫码登录演示流程。
就这样,接口设计得比较简单,签名用标准的 HMAC-SHA256,回调带重试。有啥问题可以联系我。