认证与签名
用商户 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 方法大写,如GET、POSTPATH:请求路径(不含域名与 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。
服务端校验顺序
请求依次经过:
- 假请求 IP 硬限:同一 IP 鉴权失败过多 → 429(假 ak/假签连打无法持续请求)
- Access Key 是否有效、启用
- 开发者 API 权限总闸
- 非主商户/禁用账户拒绝
- 时间戳 ±5 分钟
- Nonce 存在
- IP 白名单
- RSA 验签
- 防重放(完整签名 5 分钟内不可重复)
- 凭证限流(默认 600 次/分钟)
- 业务处理(写接口再校验幂等键)
任一步不过即拒绝,见 错误码。
限流一览
| 维度 | 限制 |
|---|---|
| 合法凭证 | 默认 600 次/分钟(控制台可配) |
GET /cards/{id}/secure | 60 次/分钟/Access Key |
| 鉴权失败(假密钥/假签名等) | 同一 IP 约 20 次/分钟 后 429 |
联调排错
签名对不上时,去控制台 → 开发者 → 日志看 SIGN_FAIL:
METHOD是否大写、PATH是否与网关 URI 一字不差timestamp/nonce请求头与签名串是否一致- body 摘要是否对真正发出的字节求的
- 公钥是否与本地私钥配对
- 出口 IP 是否在白名单
Updated about 3 hours ago
Did this page help you?
