跳转至

Authentication B 模式客户端接入文档

1. 适用范围

本文档用于客户端接入 authenticationB 模式

核心校验逻辑(B 模式):

  • validType == "B"
  • 从请求头读取签名:签名请求头名称
  • 使用 TOTP 校验:OTP:validateTOTP(secret, signature, uri)
  • time_step = 有效时间
  • authKeys = 密钥
  • uri = 请求路径
  • authKey 为数组,只要任意一个密钥校验通过即放行。

2. 客户端必备参数

配置项 说明
secret 客户端持有的鉴权密钥,必须在服务端 密钥 列表中。
uri 请求路径(不包含参数,例如https://test.com/api/order/list?id=1234中的/api/order/list)。
time_step 时间步长(秒),与平台中的 有效时间 一致。
header_name 签名请求头名(必须与 签名请求头名称 一致)。

3. 接入流程

  1. 从安全配置读取 secret
  2. 取本次请求的 path 作为 uri(如 /api/order/create)。
  3. 使用TOTP算法生成 6 位验证码(参与因子:secret + 时间戳 + uri)。
  4. 写入指定请求头(例如 x-security-auth)。
  5. 发起请求。

4. 接入示例

  • HMAC-SHA1
  • 动态截断(RFC4226 风格)
  • 6 位码
  • 消息体为:8字节counter + uri
  • secret 使用 Base64 URL 解码(与服务端对齐)
const crypto = require("crypto");

function decodeBase64Url(input) {
  const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
  const pad = "=".repeat((4 - (normalized.length % 4)) % 4);
  return Buffer.from(normalized + pad, "base64");
}

function int64ToBuffer(num) {
  const buf = Buffer.alloc(8);
  let n = BigInt(num);
  for (let i = 7; i >= 0; i--) {
    buf[i] = Number(n & 0xffn);
    n >>= 8n;
  }
  return buf;
}

function generateCode({ secret, uri, timeStep = 30, now = Math.floor(Date.now() / 1000) }) {
  const counter = Math.floor(now / timeStep);
  const key = decodeBase64Url(secret);
  const msg = Buffer.concat([int64ToBuffer(counter), Buffer.from(uri || "", "utf8")]);
  const hmac = crypto.createHmac("sha1", key).update(msg).digest();

  const offset = hmac[19] & 0x0f;
  const binary =
    ((hmac[offset] & 0x7f) << 24) |
    (hmac[offset + 1] << 16) |
    (hmac[offset + 2] << 8) |
    hmac[offset + 3];

  return String(binary % 1_000_000).padStart(6, "0");
}

async function requestWithAuth() {
  const secret = "HDA2G3TZIOUVKBWWAXX4UPAYWU";
  const uri = "/api/order/create";
  const code = generateCode({ secret, uri, timeStep: 30 });

  const res = await fetch(`https://example.com${uri}`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: code, // 需与 signatureParameterName 一致
    },
    body: JSON.stringify({ orderId: "123" }),
  });

  const body = await res.text();
  console.log(res.status, body);
}

5. 失败返回

校验失败时,服务端返回:

  • HTTP 状态码:403

6. 常见问题排查

  1. 请求头名不一致:客户端 Header 名与 签名请求头名称 不一致。
  2. URI 不一致:客户端参与签名的 path 与服务端 请求路径 不一致。
  3. time_step 不一致:客户端仍用 30 秒,但服务端已改为其他值(或反之)。
  4. 密钥不一致:客户端 secret 不在服务端 密钥 列表中。
  5. 系统时间偏差过大:客户端机器时间漂移导致验证码过期,建议开启 NTP。

7. 注意事项

  • 生产必须使用 HTTPS,避免验证码被中间人截获。
  • 测试/预发/生产使用不同密钥,避免串用。
  • 建议支持密钥轮换:服务端 authKey 同时挂新旧密钥,客户端平滑切换。