Skip to content

HMAC 认证

本页详细介绍用于认证 SlaunchX API 请求的完整 HMAC-SHA256 签名计算过程。每个发往 /api/** 端点的请求都必须使用此流程签名。

签名计算

签名计算分三步:对请求体进行哈希、构建规范消息字符串、计算 HMAC。

第 1 步:计算请求体哈希

计算原始请求体的 SHA-256 哈希,并编码为小写十六进制字符串。

bodyHash = lowercase_hex(SHA-256(requestBody))

对于有请求体的请求(POSTPUTPATCH),对实际发送的 JSON 字符串进行哈希:

bash
echo -n '{"sourceWalletId":"w_123","amount":"100.00"}' | openssl dgst -sha256 -hex
# 输出:a1fce4363854ff888cff4b8e7875d600c...

对于没有请求体的请求(GETDELETE),使用空字符串的 SHA-256 哈希:

e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

:::caution 注意 请求体编码很重要 请求体哈希是基于 HTTP 请求体中发送的精确字节序列计算的。如果您的 JSON 序列化器产生 {"a": 1}(冒号后有空格),但您对 {"a":1}(无空格)进行哈希,签名将不匹配。始终对与传给 HTTP 客户端的相同字符串进行哈希。 :::

第 2 步:构建规范消息

用换行符(\n)连接五个组件:

canonicalMessage = method + "\n" + path + "\n" + timestamp + "\n" + nonce + "\n" + bodyHash
组件说明示例
methodHTTP 方法,大写POST
path仅请求路径 -- 不含协议、主机名或查询字符串/api/v1/transfers
timestampUnix epoch 秒数(UTC),与 X-Timestamp 头的值相同1709337600
nonce唯一标识符,与 X-Nonce 头的值相同(推荐 UUID v4)550e8400-e29b-41d4-a716-446655440000
bodyHash第 1 步中的 SHA-256 十六进制摘要a1fce436...

对于 POST /api/v1/transfers 请求,规范消息如下:

POST
/api/v1/transfers
1709337600
550e8400-e29b-41d4-a716-446655440000
a1fce4363854ff888cff4b8e7875d600c...

:::caution 注意 路径不能包含查询字符串 对于带查询参数的 GET 请求(例如 /api/v1/wallets?page=0&size=20),规范消息中只包含路径部分:/api/v1/wallets。查询参数不参与签名计算。 :::

第 3 步:计算 HMAC 签名

使用 API Secret 对规范消息进行 HMAC-SHA256 签名,然后对结果进行 Base64 编码:

signature = Base64(HMAC-SHA256(canonicalMessage, apiSecret))

生成的 Base64 字符串放入 X-Signature 头。


完整示例

Bash + curl

以下脚本演示了一个完整的签名 API 请求:

bash
#!/bin/bash
# SlaunchX API 对接 -- HMAC-SHA256 认证示例

# ─── 配置 ────────────────────────────────────────────────────
API_KEY="sk_live_abc123def456"
API_SECRET="your-api-secret-here"
BASE_URL="https://api.slaunchx.cc"

# ─── 请求参数 ────────────────────────────────────────────────
METHOD="POST"
PATH_URI="/api/v1/transfer/command/create"
TIMESTAMP=$(date +%s)
NONCE=$(uuidgen | tr '[:upper:]' '[:lower:]')

# 请求体(必须与实际发送的字符串完全一致)
BODY='{"sourceWalletId":"w_123","targetWalletId":"w_456","amount":"100.00","currency":"USD"}'

# ─── 第 1 步:请求体哈希 ─────────────────────────────────────
BODY_HASH=$(echo -n "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')

# ─── 第 2 步:规范消息 ──────────────────────────────────────
CANONICAL="${METHOD}\n${PATH_URI}\n${TIMESTAMP}\n${NONCE}\n${BODY_HASH}"

# ─── 第 3 步:HMAC-SHA256 签名 ──────────────────────────────
SIGNATURE=$(echo -ne "$CANONICAL" | openssl dgst -sha256 -hmac "$API_SECRET" -binary | openssl base64 -A)

# ─── 发送请求 ────────────────────────────────────────────────
curl -X "$METHOD" "${BASE_URL}${PATH_URI}" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: $API_KEY" \
  -H "X-Signature: $SIGNATURE" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Nonce: $NONCE" \
  -d "$BODY"

对于 GET 请求,省略 -d 参数并使用空字符串的请求体哈希:

bash
METHOD="GET"
PATH_URI="/api/v1/wallets"
BODY=""
BODY_HASH="e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"

CANONICAL="${METHOD}\n${PATH_URI}\n${TIMESTAMP}\n${NONCE}\n${BODY_HASH}"
SIGNATURE=$(echo -ne "$CANONICAL" | openssl dgst -sha256 -hmac "$API_SECRET" -binary | openssl base64 -A)

curl -X "$METHOD" "${BASE_URL}${PATH_URI}" \
  -H "X-Api-Key: $API_KEY" \
  -H "X-Signature: $SIGNATURE" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Nonce: $NONCE"

Node.js

一个可复用的签名和发送 API 请求函数:

javascript
import crypto from 'crypto';

/**
 * 发送经过认证的请求到 SlaunchX API。
 *
 * @param {string} method   - HTTP 方法(GET、POST、PUT、DELETE)
 * @param {string} path     - 请求路径(例如 /api/v1/wallets)
 * @param {object|null} body - 请求体(GET/DELETE 为 null)
 * @param {string} apiKey   - 您的 API 密钥(sk_live_xxx)
 * @param {string} apiSecret - 您的 API Secret
 * @returns {Promise<object>} 解析后的 JSON 响应
 */
async function apiRequest(method, path, body, apiKey, apiSecret) {
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const nonce = crypto.randomUUID();

  // 第 1 步:请求体哈希
  const bodyStr = body ? JSON.stringify(body) : '';
  const bodyHash = crypto.createHash('sha256').update(bodyStr).digest('hex');

  // 第 2 步:规范消息
  const canonical = [method, path, timestamp, nonce, bodyHash].join('\n');

  // 第 3 步:HMAC-SHA256 签名
  const signature = crypto
    .createHmac('sha256', apiSecret)
    .update(canonical)
    .digest('base64');

  // 发送请求
  const res = await fetch(`https://api.slaunchx.cc${path}`, {
    method,
    headers: {
      'Content-Type': 'application/json',
      'X-Api-Key': apiKey,
      'X-Signature': signature,
      'X-Timestamp': timestamp,
      'X-Nonce': nonce,
    },
    body: bodyStr || undefined,
  });

  return res.json();
}

// ─── 使用示例 ──────────────────────────────────────────────

// POST:创建转账
const transfer = await apiRequest(
  'POST',
  '/api/v1/transfer/command/create',
  {
    sourceWalletId: 'w_123',
    targetWalletId: 'w_456',
    amount: '100.00',
    currency: 'USD',
  },
  'sk_live_abc123def456',
  'your-api-secret-here'
);

// GET:查询钱包列表(无请求体)
const wallets = await apiRequest(
  'GET',
  '/api/v1/wallets',
  null,
  'sk_live_abc123def456',
  'your-api-secret-here'
);

服务器验证流水线

服务器收到 API 请求时,通过多步流水线进行验证。任一步骤失败将短路处理并返回相应的错误码。

┌─────────────────────────────────────────────────────────────────┐
│                        验证流水线                                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. API 密钥查找                                                 │
│     ├── 缺少 X-Api-Key 头               →  GA2001 (401)          │
│     └── 密钥未找到                       →  GA2011 (401)          │
│                                                                 │
│  2. 密钥状态检查                                                  │
│     └── 管理员已禁用密钥                 →  GA2021 (403)          │
│                                                                 │
│  3. 工作空间解析                                                  │
│     ├── 工作空间未找到                   →  GA2032 (404)          │
│     └── 非工作空间成员                   →  GA2034 (403)          │
│                                                                 │
│  4. 时间戳验证                                                    │
│     └── |服务器时间 - 时间戳| > 60秒     →  GA2013 (401)          │
│                                                                 │
│  5. Nonce 唯一性检查                                              │
│     └── Nonce 已被使用                   →  GA2013 (401)          │
│                                                                 │
│  6. HMAC 签名验证                                                 │
│     └── 计算的 HMAC ≠ X-Signature       →  GA2012 (401)          │
│                                                                 │
│  7. IP 白名单(可选)                                              │
│     └── 客户端 IP 不在白名单中           →  GA2022 (403)          │
│                                                                 │
│  8. 权限范围检查                                                  │
│     └── 缺少所需权限范围                 →  GA2024 (403)          │
│                                                                 │
│  ✓  所有检查通过 → 处理请求                                       │
└─────────────────────────────────────────────────────────────────┘

信息 验证顺序很重要

流水线按固定顺序验证。例如,如果您的 API 密钥有效但时间戳过期,您将收到 GA2013(时间戳过期)而非签名错误。使用错误码精确诊断哪一步失败。


响应示例

成功的 API 响应:

转账成功200
{
  "version": "1.3.0",
  "timestamp": 1709337600000,
  "success": true,
  "code": "2000",
  "message": "SUCCESS",
  "data": {
    "transferId": "txn_789xyz",
    "status": "COMPLETED",
    "amount": "100.00",
    "currency": "USD"
  }
}

认证失败:

签名验证失败401
{
  "version": "1.3.0",
  "timestamp": 1709337600000,
  "success": false,
  "code": "GA2012",
  "message": "SIGNATURE_INVALID",
  "data": null
}

API 权限范围

API 密钥是工作空间级别的 — 每个密钥属于一个工作空间,只能访问该工作空间内的资源。无法通过 API 密钥进行跨工作空间访问。

每个 API 密钥分配一个或多个权限范围(scope),控制其可以访问的端点:

权限范围说明
wallet:read查询工作空间内的钱包
transfer:read查询工作空间内的转账记录
transfer:create在工作空间内的钱包之间创建转账订单

可访问端点

权限范围方法端点说明
wallet:readGET/api/v1/wallets钱包列表
wallet:readGET/api/v1/wallets/{id}钱包详情
wallet:readGET/api/v1/wallets/{id}/balance钱包余额
wallet:readGET/api/v1/wallets/{id}/flows钱包流水
transfer:readGET/api/v1/transfer/query/orders转账订单列表
transfer:readGET/api/v1/transfer/query/orders/wallet/{walletId}按钱包查询转账
transfer:readGET/api/v1/transfer/query/orders/{bizId}转账订单详情
transfer:readGET/api/v1/transfer/query/orders/{bizId}/completed检查转账完成状态
transfer:createPOST/api/v1/transfer/command/create创建转账订单

未声明 API 权限范围的端点无法通过 API 链访问(默认拒绝)。对此类端点的请求,或缺少所需权限范围的请求,将收到 403 Forbidden 响应,错误码为 50090201 (API_ACCESS_DENIED)。

工作空间隔离

所有 API 响应自动限定在 API 密钥绑定的工作空间范围内。转账查询端点强制执行工作空间归属验证 — 如果转账订单属于其他工作空间,请求将返回错误。即使使用有效的订单 ID,也能防止跨工作空间的数据泄露。


常见陷阱

:::caution 注意 时间戳时钟偏差 服务器拒绝时间戳与服务器时间相差超过 60 秒的请求。如果遇到 GA2013 错误,请检查您的服务器时钟是否通过 NTP 同步。本地调试时,将您机器上的 date +%s 与 NTP 服务器进行比较。 :::

:::caution 注意 空请求体的哈希 GETDELETE 请求没有请求体。您仍然必须在规范消息中包含请求体哈希 -- 使用空字符串的 SHA-256 值:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855。省略或使用其他值将导致 GA2012。 :::

:::caution 注意 JSON 序列化一致性 请求体哈希覆盖请求体的精确字节。如果您对一个 JSON 字符串进行哈希但 HTTP 库重新序列化了请求体(改变键顺序、空格或编码),签名将不匹配。最佳实践:序列化一次,对该字符串进行哈希,并发送同一字符串作为请求体。 :::

:::caution 注意 Nonce 重复使用 每个 nonce 在 60 秒时间戳窗口内必须全局唯一。重复使用 nonce(即使请求体不同)将导致 GA2013。使用 UUID v4 以保证唯一性。 :::

后续步骤

Last updated: