认证机制

本文档详细介绍 Zanbara API 的认证机制,包括 API Key 生成、JWT Token 获取、请求签名算法以及安全最佳实践。

认证概述

Zanbara API 使用两层认证机制:

  1. JWT Token 认证 (必需): 用于验证用户身份

  2. HMAC-SHA256 签名 (可选): 用于防止请求篡改和重放攻击

所有私有 API 端点都需要有效的 JWT Token。对于高安全性要求的场景 (如提现操作),还需要额外的请求签名验证。

认证流程图

┌─────────────┐
│  生成 API   │
│  Key/Secret │
└──────┬──────┘


┌─────────────┐
│ 请求 JWT    │
│   Token     │
└──────┬──────┘


┌─────────────┐
│ 使用 Token  │
│  调用 API   │
└──────┬──────┘


┌─────────────┐
│ (可选) 签名 │
│   请求      │
└─────────────┘

第一步: 生成 API Key

在网页端生成

  1. 登录 Zanbara 账户: https://zanbarax.com

  2. 访问 "账户设置" → "API 管理"

  3. 点击 "创建 API Key"

  4. 设置 API Key 权限和备注

权限选项:

  • 只读: 仅能查询账户、订单、仓位信息

  • 交易: 可以下单、撤单、调整仓位

  • 提现: 可以发起提现请求 (需要额外验证)

  1. (可选) 配置 IP 白名单

  2. 点击 "确认创建"

保存 API 凭证

创建成功后,您将看到:

API Key:    pk_live_1234567890abcdef1234567890abcdef
API Secret: sk_live_1234567890abcdef1234567890abcdef1234567890abcdef1234567890ab

重要提示:

  • API Secret 仅在创建时显示一次,请务必妥善保存

  • 如果遗失 Secret,需要删除旧 Key 并创建新的

  • 切勿将 Secret 提交到版本控制系统或公开仓库

API Key 格式

API Key:    pk_{env}_{32_hex_chars}
API Secret: sk_{env}_{64_hex_chars}
  • env: 环境标识

    • live - 生产环境

    • test - 测试环境

  • 后续为十六进制字符串

第二步: 获取 JWT Token

使用 API Key 和 Secret 向认证端点请求 JWT Token。

端点信息

POST /v1/auth/token

请求参数

{
  "api_key": "pk_live_1234567890abcdef1234567890abcdef",
  "api_secret": "sk_live_1234567890abcdef1234567890abcdef1234567890abcdef1234567890ab"
}

请求示例

cURL

curl -X POST https://api.zanbarax.com/v1/auth/token \
  -H "Content-Type: application/json" \
  -d '{
    "api_key": "pk_live_1234567890abcdef1234567890abcdef",
    "api_secret": "sk_live_1234567890abcdef1234567890abcdef1234567890abcdef1234567890ab"
  }'

JavaScript

const response = await fetch('https://api.zanbarax.com/v1/auth/token', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    api_key: 'pk_live_1234567890abcdef1234567890abcdef',
    api_secret: 'sk_live_1234567890abcdef1234567890abcdef1234567890abcdef1234567890ab'
  })
});

const data = await response.json();
console.log('JWT Token:', data.data.token);

Python

import requests
import json

response = requests.post(
    'https://api.zanbarax.com/v1/auth/token',
    headers={'Content-Type': 'application/json'},
    data=json.dumps({
        'api_key': 'pk_live_1234567890abcdef1234567890abcdef',
        'api_secret': 'sk_live_1234567890abcdef1234567890abcdef1234567890abcdef1234567890ab'
    })
)

data = response.json()
print('JWT Token:', data['data']['token'])

Rust

use reqwest;
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = reqwest::Client::new();

    let response = client
        .post("https://api.zanbarax.com/v1/auth/token")
        .json(&json!({
            "api_key": "pk_live_1234567890abcdef1234567890abcdef",
            "api_secret": "sk_live_1234567890abcdef1234567890abcdef1234567890abcdef1234567890ab"
        }))
        .send()
        .await?;

    let data: serde_json::Value = response.json().await?;
    println!("JWT Token: {}", data["data"]["token"]);

    Ok(())
}

响应格式

{
  "success": true,
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMzQ1Njc4OTAiLCJleHAiOjE2OTY3NTU2MDAsImlhdCI6MTY5Njc1MjAwMCwiaXNzIjoicGVyc GRleC1leGNoYW5nZSJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
    "expires_in": 3600,
    "token_type": "Bearer"
  },
  "timestamp": 1696752000000
}

字段说明:

  • token: JWT Token 字符串

  • expires_in: Token 有效期 (秒),默认 3600 (1 小时)

  • token_type: Token 类型,固定为 "Bearer"

JWT Token 结构

Zanbara 使用标准 JWT (RFC 7519) 格式:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9  ← Header (Base64)
.
eyJzdWIiOiJ1c2VyXzEyMzQ1Njc4OTAi...    ← Payload (Base64)
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk...      ← Signature (HMAC-SHA256)

Header:

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload:

{
  "sub": "user_1234567890",     // User ID
  "exp": 1696755600,            // 过期时间 (Unix timestamp)
  "iat": 1696752000,            // 签发时间
  "iss": "zanbara-exchange"     // 签发者
}

Token 刷新

Token 有效期为 1 小时。建议在 Token 过期前主动刷新:

class TokenManager {
  constructor(apiKey, apiSecret) {
    this.apiKey = apiKey;
    this.apiSecret = apiSecret;
    this.token = null;
    this.expiresAt = 0;
  }

  async getToken() {
    // 如果 Token 还有 5 分钟以上有效期,直接返回
    if (this.token && Date.now() < this.expiresAt - 300000) {
      return this.token;
    }

    // 否则获取新 Token
    const response = await fetch('https://api.zanbarax.com/v1/auth/token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        api_key: this.apiKey,
        api_secret: this.apiSecret
      })
    });

    const data = await response.json();
    this.token = data.data.token;
    this.expiresAt = Date.now() + data.data.expires_in * 1000;

    return this.token;
  }
}

// 使用示例
const tokenManager = new TokenManager(API_KEY, API_SECRET);
const token = await tokenManager.getToken();

第三步: 使用 Token 调用 API

在所有私有 API 请求中,通过 Authorization 请求头传递 JWT Token。

请求头格式

Authorization: Bearer {JWT_TOKEN}

示例: 查询账户余额

curl https://api.zanbarax.com/v1/account/balance \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

示例: 创建订单

curl -X POST https://api.zanbarax.com/v1/order/place \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json" \
  -d '{
    "symbol": "SOL-PERP",
    "side": "buy",
    "type": "limit",
    "quantity": "10",
    "price": "150.5"
  }'

认证失败处理

当 Token 无效、过期或缺失时,API 返回 401 Unauthorized:

{
  "success": false,
  "error": {
    "code": "AUTHENTICATION_FAILED",
    "message": "Invalid or expired JWT token"
  },
  "timestamp": 1696752000000
}

常见原因:

  • Token 已过期

  • Token 签名无效

  • Authorization 头格式错误

  • API Key 已被禁用或删除

处理建议:

  1. 检查 Token 是否过期,如果过期则重新获取

  2. 确认 Authorization 头格式为 Bearer {token}

  3. 确认 API Key 状态正常

请求签名 (高级安全)

对于高安全性要求的操作 (如提现),Zanbara 支持 HMAC-SHA256 请求签名。

何时需要签名

以下操作必须包含签名:

  • 提现请求 (POST /v1/account/withdraw)

  • 修改 API Key 权限

  • 删除 API Key

其他操作签名为可选,但强烈推荐用于:

  • 所有交易操作 (下单、撤单)

  • 修改杠杆、保证金模式

  • 批量操作

签名算法

步骤 1: 构造待签名字符串

{timestamp}{method}{path}{body}
  • timestamp: 当前 Unix 时间戳 (毫秒)

  • method: HTTP 方法,大写 (GET, POST, DELETE 等)

  • path: API 路径,包括查询参数

  • body: 请求体 JSON 字符串 (GET 请求为空字符串)

示例:

对于请求:

POST /v1/order/place
Body: {"symbol":"SOL-PERP","side":"buy","type":"limit","quantity":"10","price":"150.5"}

待签名字符串:

1696752000000POST/v1/order/place{"symbol":"SOL-PERP","side":"buy","type":"limit","quantity":"10","price":"150.5"}

步骤 2: 使用 HMAC-SHA256 签名

使用 API Secret 作为密钥,对待签名字符串进行 HMAC-SHA256 签名:

signature = HMAC-SHA256(api_secret, sign_string)

步骤 3: 将签名转换为十六进制字符串

hex_signature = hex(signature)

签名请求头

Authorization: Bearer {JWT_TOKEN}
X-API-Key: {API_KEY}
X-API-Signature: {HEX_SIGNATURE}
X-API-Timestamp: {TIMESTAMP}

代码示例

JavaScript

const crypto = require('crypto');

function signRequest(apiSecret, method, path, body, timestamp) {
  const bodyStr = body ? JSON.stringify(body) : '';
  const signString = `${timestamp}${method}${path}${bodyStr}`;

  const signature = crypto
    .createHmac('sha256', apiSecret)
    .update(signString)
    .digest('hex');

  return signature;
}

// 使用示例
const timestamp = Date.now();
const method = 'POST';
const path = '/v1/order/place';
const body = {
  symbol: 'SOL-PERP',
  side: 'buy',
  type: 'limit',
  quantity: '10',
  price: '150.5'
};

const signature = signRequest(API_SECRET, method, path, body, timestamp);

const response = await fetch('https://api.zanbarax.com/v1/order/place', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${jwtToken}`,
    'X-API-Key': API_KEY,
    'X-API-Signature': signature,
    'X-API-Timestamp': timestamp.toString(),
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(body)
});

Python

import hmac
import hashlib
import time
import json

def sign_request(api_secret, method, path, body, timestamp):
    body_str = json.dumps(body) if body else ''
    sign_string = f"{timestamp}{method}{path}{body_str}"

    signature = hmac.new(
        api_secret.encode('utf-8'),
        sign_string.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

    return signature

# 使用示例
timestamp = int(time.time() * 1000)
method = 'POST'
path = '/v1/order/place'
body = {
    'symbol': 'SOL-PERP',
    'side': 'buy',
    'type': 'limit',
    'quantity': '10',
    'price': '150.5'
}

signature = sign_request(API_SECRET, method, path, body, timestamp)

response = requests.post(
    'https://api.zanbarax.com/v1/order/place',
    headers={
        'Authorization': f'Bearer {jwt_token}',
        'X-API-Key': API_KEY,
        'X-API-Signature': signature,
        'X-API-Timestamp': str(timestamp),
        'Content-Type': 'application/json'
    },
    json=body
)

Rust

use hmac::{Hmac, Mac};
use sha2::Sha256;
use hex;

type HmacSha256 = Hmac<Sha256>;

fn sign_request(
    api_secret: &str,
    method: &str,
    path: &str,
    body: Option<&str>,
    timestamp: i64
) -> String {
    let body_str = body.unwrap_or("");
    let sign_string = format!("{}{}{}{}", timestamp, method, path, body_str);

    let mut mac = HmacSha256::new_from_slice(api_secret.as_bytes())
        .expect("HMAC can take key of any size");
    mac.update(sign_string.as_bytes());

    let result = mac.finalize();
    hex::encode(result.into_bytes())
}

// 使用示例
let timestamp = SystemTime::now()
    .duration_since(UNIX_EPOCH)
    .unwrap()
    .as_millis() as i64;

let method = "POST";
let path = "/v1/order/place";
let body = r#"{"symbol":"SOL-PERP","side":"buy","type":"limit","quantity":"10","price":"150.5"}"#;

let signature = sign_request(API_SECRET, method, path, Some(body), timestamp);

let response = client
    .post("https://api.zanbarax.com/v1/order/place")
    .header("Authorization", format!("Bearer {}", jwt_token))
    .header("X-API-Key", API_KEY)
    .header("X-API-Signature", signature)
    .header("X-API-Timestamp", timestamp.to_string())
    .header("Content-Type", "application/json")
    .body(body)
    .send()
    .await?;

签名验证失败

当签名验证失败时,API 返回 401 Unauthorized:

{
  "success": false,
  "error": {
    "code": "INVALID_SIGNATURE",
    "message": "Request signature verification failed"
  },
  "timestamp": 1696752000000
}

常见原因:

  • 时间戳与服务器时间相差超过 5 秒

  • 签名字符串构造错误

  • 使用错误的 API Secret

  • 请求体格式不一致 (空格、换行等)

调试建议:

  1. 打印待签名字符串,确认格式正确

  2. 确认时间戳单位为毫秒

  3. 确认请求体 JSON 格式与签名时一致 (不含额外空格)

  4. 使用测试端点验证签名算法

安全最佳实践

1. 保护 API 凭证

推荐做法:

// 使用环境变量
const API_KEY = process.env.ZANBARA_API_KEY;
const API_SECRET = process.env.ZANBARA_API_SECRET;

避免做法:

// 切勿硬编码
const API_KEY = 'pk_live_1234567890abcdef1234567890abcdef';
const API_SECRET = 'sk_live_1234567890...'; // 危险!

2. 使用不同权限的 API Key

策略机器人: 只读权限
交易机器人: 交易权限
后台管理: 提现权限 (需额外签名)

3. 配置 IP 白名单

在 API Key 设置中限制允许的 IP 地址:

服务器 IP: 203.0.113.10
办公室网络: 198.51.100.0/24

4. 定期轮换 API Key

建议每 90 天轮换一次 API Key:

// 同时支持新旧两个 Key,平滑过渡
const PRIMARY_KEY = process.env.ZANBARA_API_KEY_PRIMARY;
const BACKUP_KEY = process.env.ZANBARA_API_KEY_BACKUP;

async function callAPI(endpoint, data) {
  try {
    return await callWithKey(PRIMARY_KEY, endpoint, data);
  } catch (error) {
    if (error.code === 'AUTHENTICATION_FAILED') {
      // 主 Key 失败,尝试备用 Key
      return await callWithKey(BACKUP_KEY, endpoint, data);
    }
    throw error;
  }
}

5. 监控 API Key 使用

定期检查 API Key 使用日志:

  • 异常 IP 访问

  • 异常时间段活动

  • 失败请求激增

6. 及时吊销泄露的 Key

如果怀疑 API Key 泄露:

  1. 立即在网页端禁用该 Key

  2. 创建新的 API Key

  3. 更新应用程序配置

  4. 检查账户异常活动

7. 使用 HTTPS

所有 API 请求必须使用 HTTPS:

✓ https://api.zanbarax.com/v1/...
✗ http://api.zanbarax.com/v1/...  (将被拒绝)

8. 实现请求重放保护

使用 client_order_id 防止订单重复:

function createOrder(params) {
  return {
    ...params,
    client_order_id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
  };
}

9. 限制 Token 作用域

如果可能,为不同环境使用不同的 API Key:

开发环境: 使用测试网 API Key
生产环境: 使用生产网 API Key,严格访问控制

故障排查

问题 1: "Invalid API Key"

原因:

  • API Key 格式错误

  • API Key 已被删除或禁用

  • 使用了测试环境 Key 访问生产环境

解决方法:

  1. 检查 API Key 格式: pk_{env}_{32_hex_chars}

  2. 在网页端确认 Key 状态

  3. 确认环境匹配 (test/live)

问题 2: "Token Expired"

原因:

  • JWT Token 超过 1 小时有效期

解决方法:

// 实现自动刷新
if (error.code === 'TOKEN_EXPIRED') {
  token = await refreshToken();
  return retryRequest();
}

问题 3: "Timestamp Out of Range"

原因:

  • 本地时钟与服务器时间相差超过 5 秒

解决方法:

# 同步系统时钟
ntpdate -u time.apple.com

# 或使用服务器时间
curl https://api.zanbarax.com/v1/system/time

问题 4: "Signature Verification Failed"

原因:

  • 签名算法实现错误

  • 请求体被修改

  • 时间戳不匹配

解决方法:

  1. 打印待签名字符串,对比预期格式

  2. 确认签名和请求使用相同的 body

  3. 确认时间戳一致

测试工具

签名测试端点

Zanbara 提供签名测试端点,用于验证签名算法:

POST /v1/auth/test-signature

请求:

{
  "timestamp": 1696752000000,
  "method": "POST",
  "path": "/v1/order/place",
  "body": "{\"symbol\":\"SOL-PERP\"}",
  "signature": "abc123..."
}

响应:

{
  "success": true,
  "data": {
    "signature_valid": true,
    "expected_signature": "abc123...",
    "your_signature": "abc123...",
    "sign_string": "1696752000000POST/v1/order/place{\"symbol\":\"SOL-PERP\"}"
  }
}

Postman Collection

我们提供预配置的 Postman Collection,包含签名脚本:

下载 Postman Collection

相关文档


文档版本: v1.0.0 最后更新: 2025-10-07 维护: Zanbara API Team

Last updated