# 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# 侧逐字节对拍。