Authentication B 模式客户端接入文档¶
1. 适用范围¶
本文档用于客户端接入 authentication 的 B 模式。
核心校验逻辑(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. 接入流程¶
- 从安全配置读取
secret。 - 取本次请求的 path 作为
uri(如/api/order/create)。 - 使用TOTP算法生成 6 位验证码(参与因子:
secret + 时间戳 + uri)。 - 写入指定请求头(例如
x-security-auth)。 - 发起请求。
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. 常见问题排查¶
- 请求头名不一致:客户端 Header 名与
签名请求头名称不一致。 - URI 不一致:客户端参与签名的 path 与服务端
请求路径不一致。 - time_step 不一致:客户端仍用 30 秒,但服务端已改为其他值(或反之)。
- 密钥不一致:客户端
secret不在服务端密钥列表中。 - 系统时间偏差过大:客户端机器时间漂移导致验证码过期,建议开启 NTP。
7. 注意事项¶
- 生产必须使用 HTTPS,避免验证码被中间人截获。
- 测试/预发/生产使用不同密钥,避免串用。
- 建议支持密钥轮换:服务端
authKey同时挂新旧密钥,客户端平滑切换。