认证与签名
接入 OristaPay Open API 采用 双重认证:
- OAuth2 访问令牌 — 证明调用方身份
- HMAC 请求签名 — 证明请求未被篡改
每次请求必须同时通过两层校验,任一失败即返回 401 Unauthorized。
1. 获取访问令牌
标准 OAuth2 client_credentials 流程。
请求
POST /realms/digitalasset/protocol/openid-connect/token HTTP/1.1
Host: auth.uat.rdezlink.tech
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id={api_key}
&client_secret={api_secret}
响应
{
"access_token": "eyJhbGciOi...",
"token_type": "Bearer",
"expires_in": 300
}
使用约束
access_token 在 expires_in 秒内可复用,建议缓存并在到期前 30 秒主动刷新
access_token 必须由与 X-Api-Key 对应的 OAuth 客户端签发
使用 A 商户的 api_key 配合 B 商户的 access_token 调用会被拒绝。
2. 请求签名算法
待签字符串拼接规则
string_to_sign = METHOD + PATH + TIMESTAMP + NONCE + SHA256_HEX(BODY)
签名计算
signature = HEX( HMAC_SHA256( sign_secret, string_to_sign ) )
字段定义
| 元素 | 定义 |
|---|
METHOD | 请求方法,全大写,如 POST |
PATH | 接口路径,不含域名与 query,如 /api/v1/wallet/list |
TIMESTAMP | 13 位 UTC 毫秒时间戳字符串,与 X-Timestamp 完全一致 |
NONCE | 本次请求唯一随机串,与 X-Nonce 完全一致,建议 32 位 hex |
BODY | 请求体原始字节;无 body 时为空字符串(SHA256_HEX("") = e3b0c442...b855) |
参考实现
import hashlib
import hmac
def sign(method: str, path: str, ts: str, nonce: str,
body: str, sign_secret: str) -> str:
body_hash = hashlib.sha256(body.encode()).hexdigest()
to_sign = method + path + ts + nonce + body_hash
return hmac.new(sign_secret.encode(),
to_sign.encode(),
hashlib.sha256).hexdigest()
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.util.HexFormat;
public static String sign(String method, String path, String ts,
String nonce, byte[] body, String signSecret)
throws Exception {
String bodyHash = HexFormat.of().formatHex(
MessageDigest.getInstance("SHA-256").digest(body));
String toSign = method + path + ts + nonce + bodyHash;
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(signSecret.getBytes(), "HmacSHA256"));
return HexFormat.of().formatHex(mac.doFinal(toSign.getBytes()));
}
const crypto = require('crypto');
function sign(method, path, ts, nonce, body, signSecret) {
const bodyHash = crypto.createHash('sha256').update(body).digest('hex');
const toSign = method + path + ts + nonce + bodyHash;
return crypto.createHmac('sha256', signSecret)
.update(toSign).digest('hex');
}
3. 请求头规范
所有业务请求必须同时携带以下请求头:
| Header | 必选 | 说明 |
|---|
Authorization | ✓ | Bearer {access_token} |
X-Api-Key | ✓ | 接入方 api_key |
X-Timestamp | ✓ | UTC 毫秒时间戳,允许 ±5 分钟 偏差 |
X-Nonce | ✓ | 本次请求唯一随机串,5 分钟内同 api_key 下不可复用 |
X-Signature | ✓ | §2 计算出的签名 |
Content-Type | ✓ | 固定 application/json; charset=utf-8 |
请求与响应约定
协议规范
| 项目 | 规范 |
|---|
| 传输协议 | HTTPS (TLS 1.2+) |
| 请求方法 | POST |
| 字符集 | UTF-8 |
| 请求/响应 | JSON |
| 字段命名 | camelCase |
| 时间戳 | 毫秒;请求侧字符串/数字皆可,响应侧统一数字 |
响应信封
所有响应均为单层 JSON { code, message, data? },由下游业务服务返回,网关不再做二次包装。
成功(业务正常)
业务码 code = 1,业务数据在 data 中:
{
"code": 1,
"message": "Success",
"data": { /* 业务数据;列表接口为数组 */ }
}
业务/下游错误
下游返回业务错误,HTTP 状态码仍为 200,错误细节由业务码 code / message 表达:
{
"code": <非 1 整数>,
"message": "<失败原因>"
}
下游 gRPC 不可达 / 超时等场景,HTTP 状态码仍为 200,code 为负整数(与 gRPC StatusCode 取负,如 -14 表示 UNAVAILABLE),message 给出连接错误详情。
网关层错误
路由未命中、网关内部异常、descriptor 缺失等。HTTP 状态码使用对应错误码(如 400 / 404 / 500 / 502),响应体仍为单层 {code, message}:
{
"code": 404,
"message": "route not found"
}
- HTTP 状态码反映传输层处理结果:
2xx 表示请求成功送达下游并完成转换;非 2xx 表示网关侧错误
- 业务码
code 反映业务层处理结果:1 表示业务成功,data 承载返回值;其他值为业务错误码,message 给出原因描述
- 判断成功的正确姿势:HTTP
2xx + 业务 code == 1,两者皆满足才表示业务调用成功
- 认证类错误:
Authorization / X-Signature / X-Timestamp / X-Nonce 任一校验失败统一返回 HTTP 401,响应体为 {"code":401,"message":"Unauthorized"}
错误处理
HTTP 状态码与业务码
响应体统一为单层 {code, message, data?}。HTTP 状态码反映网关/传输层结果,业务码 code 反映业务处理结果。
| HTTP Status | 含义 | 响应体 | 建议 |
|---|
200 + code == 1 | 业务成功 | {code:1, message, data} | 从 data 取业务字段 |
200 + code != 1 | 下游业务错误 | {code, message} | 按 message 处理业务错误 |
200 + code < 0 | 请求送达后 gRPC 异常 | {code, message}(code 为 gRPC 状态码取负) | 检查下游可用性,必要时重试 |
400 | 请求体或参数非法 | {code, message} | 修正后重试 |
401 | 认证失败(OAuth 或签名) | {code:401, message} | 按下方排查清单逐项检查 |
404 | 接口不存在 | {code, message} | 核对路径 |
429 | 触发限流 | {code, message, ...} | 退避后重试 |
5xx | 网关或下游服务异常 | {code, message} | 指数退避后重试 |
认证失败排查清单
所有认证类错误统一返回:
{ "code": 401, "message": "Unauthorized" }
请按以下顺序排查:
- Token 是否有效 — 是否过期?是否由当前
api_key 签发?
- 签名是否匹配 —
METHOD / PATH / BODY 是否与实际发送一致?
- 时间戳是否在窗口内 — 本机时钟是否与 NTP 同步?
- Nonce 是否唯一 — 5 分钟内同一
api_key 下不得复用
限流策略
| 项 | 值 |
|---|
| 维度 | 按 api_key 独立计量 |
| 默认配额 | 600 次 / 分钟 |
| 超限响应 | HTTP 429 |
超限响应示例
{
"code": 429,
"message": "rate limit exceeded",
"limit": 600,
"window_ms": 60000
}
需要更高配额请联系商务,调整后次分钟生效。
安全建议
| 主题 | 最佳实践 |
|---|
| 密钥管理 | api_secret 与 sign_secret 仅在服务端使用,严禁下发到前端或移动端 |
| 凭证轮换 | 定期轮换;发现泄露立即联系商务重置 |
| 传输安全 | 强制 HTTPS,拒绝任何未启用 TLS 1.2+ 的客户端 |
| 时钟同步 | 使用 NTP 保持 ±1 分钟内的精度,避免时钟漂移引发误判 |
| 日志脱敏 | 应用日志中不得保存完整 api_secret / sign_secret / access_token |