谈谈开放平台扫码登录的接入设计

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. 注册应用

调用注册接口拿到 appKeyappSecret

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,回调带重试。有啥问题可以联系我。

加载评论中...