认证与签名

用商户 RSA 私钥对每个请求加签,服务端验签放行

认证与签名

CAFINX 开放接口对每个请求做 RSA 验签。你用商户私钥加签,服务端用你上传的商户公钥验签。

Base URL

环境Base URL
沙箱https://uat-openapi.cafinx.com/v1
生产https://openapi.cafinx.com/v1

请求完整 URL = Base URL + 资源路径(如 /ping/cards)。签名 PATH 用网关视角路径(通常为 /v1/...不含 query)。

必带请求头

请求头说明
X-Access-Key凭证 Access Key,以 ak_ 开头
X-Timestamp毫秒级时间戳,与服务器时间相差不超过 ±5 分钟
X-Nonce每次请求全局唯一的随机串(建议 UUID)
X-Signature对签名串做 RSA-SHA256 后 Base64 编码的结果

写接口(POST)额外:

请求头说明
X-Idempotency-Key幂等键,≤64 字符;必填。同键 24h 内重复请求回放首次响应;同键不同 body → 409 冲突

签名串构造

按顺序用 \n(换行符)拼接 5 段:

签名串 = METHOD + "\n" + PATH + "\n" + timestamp + "\n" + nonce + "\n" + sha256Hex(body)
  • METHOD:HTTP 方法大写,如 GETPOST
  • PATH:请求路径(不含域名与 query),以网关收到的为准(例如 /v1/ping 或本地 /v1/gateway/v1/ping——与你实际请求 URI 一致
  • timestamp / nonce:与同名请求头完全一致
  • sha256Hex(body):请求体原文的 SHA-256 十六进制小写;GET 无 body 时对空串 "" 取摘要

然后:

X-Signature = Base64( RSA-SHA256( 签名串, 商户私钥 ) )

关键:sha256Hex(body) 必须对你实际发送的那串字节求摘要。序列化后不要再改动空格或字段顺序。

代码示例

Node.js

const crypto = require('crypto');

function buildHeaders({ method, path, body, accessKey, privateKeyPem, idempotencyKey }) {
  const timestamp = Date.now().toString();
  const nonce = crypto.randomUUID();
  const bodyStr = body ? JSON.stringify(body) : '';
  const bodyHash = crypto.createHash('sha256').update(bodyStr, 'utf8').digest('hex');
  const signingString = [method.toUpperCase(), path, timestamp, nonce, bodyHash].join('\n');
  const signature = crypto
    .sign('RSA-SHA256', Buffer.from(signingString, 'utf8'), privateKeyPem)
    .toString('base64');
  const headers = {
    'X-Access-Key': accessKey,
    'X-Timestamp': timestamp,
    'X-Nonce': nonce,
    'X-Signature': signature,
    'Content-Type': 'application/json',
  };
  if (idempotencyKey) headers['X-Idempotency-Key'] = idempotencyKey;
  return headers;
}

Python

完整客户端见仓库 docs/developer-sdk/python/cafinx_client.py

import time, uuid, hashlib, base64
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding

timestamp = str(int(time.time() * 1000))
nonce = str(uuid.uuid4())
body_str = body or ""
body_hash = hashlib.sha256(body_str.encode()).hexdigest()
signing_string = "\n".join([method.upper(), path, timestamp, nonce, body_hash])

signature = base64.b64encode(
    private_key.sign(signing_string.encode(), padding.PKCS1v15(), hashes.SHA256())
).decode()

Java

完整客户端见仓库 docs/developer-sdk/java/CafinxClient.java

服务端校验顺序

请求依次经过:

  1. 假请求 IP 硬限:同一 IP 鉴权失败过多 → 429(假 ak/假签连打无法持续请求)
  2. Access Key 是否有效、启用
  3. 开发者 API 权限总闸
  4. 非主商户/禁用账户拒绝
  5. 时间戳 ±5 分钟
  6. Nonce 存在
  7. IP 白名单
  8. RSA 验签
  9. 防重放(完整签名 5 分钟内不可重复)
  10. 凭证限流(默认 600 次/分钟)
  11. 业务处理(写接口再校验幂等键)

任一步不过即拒绝,见 错误码

限流一览

维度限制
合法凭证默认 600 次/分钟(控制台可配)
GET /cards/{id}/secure60 次/分钟/Access Key
鉴权失败(假密钥/假签名等)同一 IP 约 20 次/分钟429

联调排错

签名对不上时,去控制台 → 开发者 → 日志SIGN_FAIL

  • METHOD 是否大写、PATH 是否与网关 URI 一字不差
  • timestamp/nonce 请求头与签名串是否一致
  • body 摘要是否对真正发出的字节求的
  • 公钥是否与本地私钥配对
  • 出口 IP 是否在白名单

Did this page help you?