API 签名与鉴权
两种身份
| 身份 | 凭据 | 用途 | ||
|---|---|---|---|---|
| 应用级 | AK/SK 签名头 | 服务端直连子平台(读写业务数据) | ||
| 用户级 | Bearer <token> | 代表具体用户(登录态操作) |
两种可以同时携带,网关 OR 语义校验;业务平台只消费网关注入的 X-Appid / X-User-Id。
签名算法
StringToSign = METHOD \n Path \n CanonicalQuery \n BodyHash \n Timestamp \n Nonce
Signature = Base64( HMAC-SHA256( sk, StringToSign ) )
各字段:
| 字段 | 说明 | ||
|---|---|---|---|
METHOD | GET / POST / PUT / PATCH / DELETE(大写) | ||
Path | 请求路径(不含 query),如 /content/v1/contents/by-slug/hello | ||
CanonicalQuery | query 按 key 字典序,k=v 用 & 连接(值 URL 编码) | ||
BodyHash | SHA256(body) 十六进制;无 body 时为空串哈希 | ||
Timestamp | Unix 秒 | ||
Nonce | 随机串(防重放) |
请求头
| 头 | 值 | ||
|---|---|---|---|
X-AK | ak | ||
X-Signature | Base64 签名 | ||
X-Timestamp | Unix 秒 | ||
X-Nonce | 随机串 | ||
Content-Type | application/json(有 body 时) | ||
Authorization | Bearer {token}(可选,用户级) | ||
Idempotency-Key | 可选,写操作幂等(强烈建议) |
示例:Node.js
const crypto = require('node:crypto');
function signRequest({ method, path, query = {}, body = '', ak, sk, timestamp, nonce }) {
const canon = Object.keys(query).sort()
.map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(String(query[k]))}`).join('&');
const bodyHash = crypto.createHash('sha256').update(body).digest('hex');
const sts = [method.toUpperCase(), path, canon, bodyHash, timestamp, nonce].join('\n');
return {
'X-AK': ak,
'X-Signature': crypto.createHmac('sha256', sk).update(sts).digest('base64'),
'X-Timestamp': String(timestamp),
'X-Nonce': nonce,
};
}
示例:PHP(Laravel)
$sts = implode("\n", [
strtoupper($method), $path, $canonicalQuery,
hash('sha256', $body ?? ''), $timestamp, $nonce,
]);
$signature = base64_encode(hash_hmac('sha256', $sts, $sk, true));
// 请求头:X-AK / X-Signature / X-Timestamp / X-Nonce
错误格式
所有错误统一返回:
{ "error": { "code": "CONTENT_NOT_FOUND", "message": "内容不存在", "request_id": "..." } }
HTTP 状态码语义化:400 参数错、401 签名/令牌无效、403 无 scope、404 不存在、409 冲突、422 校验失败。
幂等写入
写操作(POST/PUT/PATCH/DELETE)建议携带 Idempotency-Key。相同 key 的重复请求会返回首次结果,防止重试造成重复创建。key 建议用业务语义唯一串(例如 order-{userId}-{productId}-{ts})。
⚠️ 教训案例:广告 SDK 曾用 ticket.slice(0,40) 当幂等键,同一投放的票据前 40 字符完全相同,导致曝光全部被去重。幂等键必须对每次业务事件唯一。