文档 / 平台架构

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 ) )

各字段:

字段说明
METHODGET / POST / PUT / PATCH / DELETE(大写)
Path请求路径(不含 query),如 /content/v1/contents/by-slug/hello
CanonicalQueryquery 按 key 字典序,k=v& 连接(值 URL 编码)
BodyHashSHA256(body) 十六进制;无 body 时为空串哈希
TimestampUnix 秒
Nonce随机串(防重放)

请求头

X-AKak
X-SignatureBase64 签名
X-TimestampUnix 秒
X-Nonce随机串
Content-Typeapplication/json(有 body 时)
AuthorizationBearer {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 字符完全相同,导致曝光全部被去重。幂等键必须对每次业务事件唯一