优化签名排序逻辑

This commit is contained in:
yml2213
2026-07-20 16:34:23 +08:00
parent e4d0a71963
commit ee78b80cd3
3 changed files with 108 additions and 54 deletions
+55 -22
View File
@@ -1,6 +1,6 @@
# 店铺订单对接 API 说明(发给皮肤源头)
# 店铺订单对接 API 文档
> 文档对象:皮肤源头 / 发货系统技术人员
> 文档对象:发货系统技术人员
> 版本:v1
> 更新说明:鉴权为 **ApiKey + HMAC 签名**,请按本文实现,勿只传 Key。
@@ -16,13 +16,13 @@
---
## 1. 环境信息(由店铺方填写后发给你们)
## 1. 环境信息
| 项 | 值(示例 / 请替换) |
|----|---------------------|
| **API 根地址 Base URL** | `https://api.example.com`(开发:`http://主机:8080` |
| **X-Api-Key** | 由店铺方分配,如 `sk_xxxx` |
| **api_secret** | 由店铺方单独发送,**只用于本地算签名,不要写在 URL/Header** |
| **API 根地址 Base URL** | `https://http://221329.cc.cd` 开发临时地址 |
| **X-Api-Key** | 测试使用,如 `sk_source_dev_key_3ad3d1bbacda0e54` |
| **api_secret** | 测试使用,**只用于本地算签名,不要写在 URL/Header**, `sk_source_dev_secret_5a43ec55cd020d83` |
| **时间偏差** | 默认允许 ±300 秒 |
统一响应格式:
@@ -49,23 +49,43 @@
| `X-Nonce` | 是 | 随机串,长度 8~64;同一 Key 在有效期内不可重复 |
| `X-Sign` | 是 | 见下方算法 |
### 2.1 签名字符串(6 行,用 `\n` 接)
### 2.1 签名字符串(参数字典序 + `&` 接)
参与签名的参数:
| 参数名 | 说明 |
|--------|------|
| `api_key` | 与 Header `X-Api-Key` 相同 |
| `timestamp` | 与 Header `X-Timestamp` 相同 |
| `nonce` | 与 Header `X-Nonce` 相同 |
| `method` | 大写:`GET` / `POST` |
| `path` | 仅路径,**不要**域名、**不要** query。例:`/api/open/v1/orders/O123` |
| `body` | 原始 HTTP bodyGET 用**空字符串** |
规则:
1. value **原样拼接,不做 URL encode**
2. 按参数名 **ASCII 字典序** 排序
3. 拼成:`k1=v1&k2=v2&k3=v3...`
4. `X-Sign = hex( HMAC-SHA256( api_secret, 签名字符串 ) )`**小写**十六进制
排序后参数名顺序固定为:
```text
{api_key}
{timestamp}
{nonce}
{METHOD}
{path}
{body}
api_key, body, method, nonce, path, timestamp
```
| 字段 | 规则 |
|------|------|
| METHOD | 大写:`GET` / `POST` |
| path | 仅路径,**不要**域名、**不要** query。例:`/api/open/v1/orders/O123` |
| body | 原始 HTTP body 字符串;GET 用**空字符串** |
| X-Sign | `hex( HMAC-SHA256( api_secret, 签名字符串 ) )`**小写**十六进制 |
**GET 示例**body 为空):
```text
api_key=sk_xxx&body=&method=GET&nonce=a1b2c3d4e5f67890&path=/api/open/v1/orders/O202607201550038000&timestamp=1721450000
```
**POST 示例**
```text
api_key=sk_xxx&body={"order_no":"O202607201550038000","ship_status":"success"}&method=POST&nonce=a1b2c3d4e5f67890&path=/api/open/v1/orders/ship-notify&timestamp=1721450000
```
### 2.2 Python 参考实现
@@ -76,10 +96,22 @@ API_KEY = "请替换为店铺下发的 key"
API_SECRET = "请替换为店铺下发的 secret"
BASE = "https://api.example.com" # 请替换
def build_sign_string(api_key: str, timestamp: str, nonce: str, method: str, path: str, body: str = "") -> str:
params = {
"api_key": api_key,
"body": body,
"method": method.upper(),
"nonce": nonce,
"path": path,
"timestamp": timestamp,
}
# 字典序 + & 拼接,value 不 encode
return "&".join(f"{k}={params[k]}" for k in sorted(params.keys()))
def sign_headers(method: str, path: str, body: str = "") -> dict:
ts = str(int(time.time()))
nonce = uuid.uuid4().hex
raw = "\n".join([API_KEY, ts, nonce, method.upper(), path, body])
raw = build_sign_string(API_KEY, ts, nonce, method, path, body)
sign = hmac.new(API_SECRET.encode(), raw.encode(), hashlib.sha256).hexdigest()
return {
"X-Api-Key": API_KEY,
@@ -101,8 +133,9 @@ print(requests.post(BASE + path, headers=headers, data=body.encode("utf-8")).jso
**注意:**
- POST 时 `json.dumps` 后的字符串要原样发送,不要一边签名一边再改空格/字段顺序。
- 签名失败常见原因:secret 错、path 多了 query、body 不一致、时间戳过期、nonce 重复。
- POST 时签名用的 `body` 必须与实际发送的 body **字节级一致**不要一边签名一边再改空格/字段顺序
- value **不要** URL encode。
- 签名失败常见原因:secret 错、path 多了 query、body 不一致、时间戳过期、nonce 重复、参数未按字典序拼接。
---