bag
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
# yh-platform 签名协议规范(v1.0)
|
||||
|
||||
> 权威源:Java `yh-utils-core` `utils.crypto.SignUtil` + `yh-app-auth-spring-boot-starter` `SignCanonical`。
|
||||
> 本文档为双端(Java / C#)共同遵守的契约工件;任何变更须先改本文,再同步两侧实现。
|
||||
|
||||
## 1. 算法
|
||||
|
||||
统一使用 **HmacSHA256**,密钥与签名串均为 UTF-8 字节。
|
||||
|
||||
## 2. 双签名规则(长期并存,见方案 9.5 裁决 #5 收敛计划)
|
||||
|
||||
### 2.1 数据级签名(业务内容签名)
|
||||
|
||||
```
|
||||
source = data + "|" + timestamp
|
||||
output = HmacSHA256(source, secret) 的小写十六进制
|
||||
timestamp = 毫秒级时间戳字符串
|
||||
```
|
||||
|
||||
- 验签忽略大小写比对(`equalsIgnoreCase`)。
|
||||
- `verifyWithExpire`:先比对签名,再校验 `|now - ts| <= expireMs`(默认 5 分钟,双向偏差均拒绝)。
|
||||
|
||||
### 2.2 请求级签名(HTTP 请求签名,SDK 出站默认)
|
||||
|
||||
```
|
||||
canonicalTarget = path + ("?" + sortedQuery # query 为空则不带
|
||||
source = METHOD + "\n" + canonicalTarget + "\n" + timestamp
|
||||
output = HmacSHA256(source, secret) 的 Base64
|
||||
```
|
||||
|
||||
- `METHOD`:大写(GET/POST),**大小写敏感**精确比对(这是既有行为,是否收敛见方案 9.5 裁决 #3)。
|
||||
- `path`:**只取路径**,不含 host/port/context-path(保证服务发现/反代前后端天然对齐)。
|
||||
- **basePath 剥离(客户端与服务端共同口径)**:客户端签名前从 path 剥离部署 basePath(与 UserBase 部署 context-path 一致,如 `/userbase`,对齐 Java `AppSignInterceptor.stripBasePath`);服务端验签前剥 `request.getContextPath()`(对齐 Java `AppAuthFilter`)。剥离后 canonical 口径统一为 `/oapi/**`。剥离规则:path 不以 basePath 开头时原样保留;剥离后为空或丢失前导斜杠时补 `/`(`/userbase` → `/`)。
|
||||
- `sortedQuery`:query 按 `k=v` 整串字典序排序后以 `&` 连接;k、v 先 URL 解码再参与排序;多值参数的值也排序。
|
||||
- 验签为**精确比对**(Base64 区分大小写)。
|
||||
- `verifyRequestWithExpire` 语义同数据级。
|
||||
- 测试向量中的 `canonicalTarget` 即**剥离后**的服务端口径(含 `fullUrl`/`basePath` 字段可复现剥离前 URL)。
|
||||
|
||||
### 2.3 密钥约束
|
||||
|
||||
- secret 最小长度 **16** 位(`MIN_SECRET_LENGTH`),低于此值签名方法抛参数异常。
|
||||
|
||||
## 3. HTTP 头契约(逐字一致)
|
||||
|
||||
| 头名 | 值 |
|
||||
|---|---|
|
||||
| `X-App-Key` | 调用方在 UserBase 注册的 appKey |
|
||||
| `X-Timestamp` | 毫秒级时间戳字符串 |
|
||||
| `X-Signature` | 请求级签名(Base64) |
|
||||
|
||||
## 4. 防重放
|
||||
|
||||
- 时间窗口:`|now - X-Timestamp| > expireMs`(默认 **300000ms**)即拒绝。
|
||||
- nonce(可选扩展):同一 `(appKey, timestamp, nonce)` 在窗口内只允许提交一次;
|
||||
Redis 键 `sign:replay:{appKey}:{timestamp}:{nonce}`,SETNX + TTL=窗口。
|
||||
|
||||
## 5. 验签密钥解析链(服务端)
|
||||
|
||||
```
|
||||
1. Redis key = sign:app:{appKey} → 值为 SignAppKeyPayload JSON {appKey, secret, enabled}
|
||||
2. 进程内缓存(Caffeine / IMemoryCache,TTL 5min)
|
||||
3. 兜底 GET {userbase}/oapi/internal/sign-key/{appKey} → 信封 data 为 SignAppKeyPayload
|
||||
```
|
||||
|
||||
- 仅当 `enabled=true` 且 `secret` 非空才可用;未知 appKey 直接拒绝。
|
||||
- 任一层异常静默降级到下一层(Redis 宕机不阻断业务)。
|
||||
|
||||
## 6. 测试向量
|
||||
|
||||
`fixtures/sign-vectors.json`:≥10 组向量(含空 body、超长参数、中文 UTF-8、query 排序、密钥长度边界、毫秒时间戳边界)。
|
||||
**基线由 Java `SignUtil` 实际运行产出**;CI 中 Java 侧重算 fixture 防漂移,C# 侧逐字节对拍。
|
||||
Reference in New Issue
Block a user