# HMAC 签名 + JWT → token 令牌体系迁移对照

> 状态：**已落地**（token 体系为唯一鉴权体系，旧体系完全退役）
> 关联：[token.md](token.md)（体系总文档）、`pylon-fastify/src/common/auth/TokenStrategy.ts`（auth 链实现）

旧体系是**两个互不相关的层**：HMAC 签名层证明"请求来自合法客户端"，JWT 登录层证明"用户是谁"——两条路径、两套密钥、两套过期与吊销管理。token 体系把它们合并为一个**两态对象**：签名材料（security 段）与身份数据（identity 段）统一由 Redis 平面对象承载，验签与认证一次完成。

## 组件对照

| 旧体系（HMAC + JWT） | token 体系 | 说明 |
|---------------------|-----------|------|
| `sign/issue` + `sign/refresh` 凭证引导端点（SignController） | **退役** | 无公共签发接口；每个 app 的登录入口（`@LoginEntry` 标记的 login + refresh 双端点）即入口 |
| `schema/api_keys.table.ts`（appKey/appSecret 存储） | **退役** | 会话凭据（token + refresh_token + login_at）存账户表（身份表硬约束三列） |
| HMAC 签名头：`x-app-key` / `x-timestamp` / `x-nonce` / `x-signature` | `x-token` / `x-timestamp` / `x-nonce` / `x-signature` | `x-app-key` 换成 `x-token`；timestamp + nonce 防重放**保留**；签名串格式不变（method + path + timestamp + nonce + canonical body） |
| 签名密钥：按 appKey 查密钥表（sign-db-driver / sign-redis-driver） | Redis 对象 secret 字段（`{app}.{token}` 一次还原） | 验签材料与身份同对象，零额外查询 |
| JWT：`Authorization: Bearer <token>`，payload 携带 user 对象 | token 纯随机 hash（`x-token` 头） | 不可解析、无 payload/exp，客户端只见 hash |
| `signToken` / `verifyToken` / blacklist 检查 | **退役** | 删 Redis key 即吊销，blacklist 补丁不再需要 |
| JWT secret 一套密钥 | secret/cipher 随 token 对象存 Redis（login 响应下发 secret） | 每会话独立密钥，轮换随 token 走 |
| `@Public()`（sign/issue 唯一豁免 + 第三方回调） | **保留，仅第三方回调白名单** | sign/issue 豁免用途退役；`@Public` 原语义不变（跳过签名检查），使用范围收敛为第三方回调 controller（thirdApis 名下模块）唯一白名单（lint 强制） |
| `@Login('<模块名>')` + `user.type` 403 判定 | `@Login('<app>')` 语义保留 | module = app 名，兼作 Redis key 前缀（app 命名空间隔离） |
| 无登录入口标记 | **`@LoginEntry(app?)`**（新增） | 标记登录入口 controller（login + refresh 双端点），auth 链据此 + body 是否带 refreshToken 区分验证模式 |
| `(body, user: User)` handler 第二参 | `(body, token: {Name}Token)` | User 退役；按 api+app 自动推导模块 token 类型（无 token 声明 = 生成报错） |
| `__inject` 只映射一个 id（`(body, user)`） | `fromToken(token, [...])` 多属性注入（`(body, token)`） | 服务器注入字段从 Token 平面对象填充 |
| refreshToken：无此概念（JWT 过期重新登录） | refreshToken 7 天滑动窗口 + 每次刷新轮换 | 只在 login 返回；**只在 refresh 请求体中上送**（不出现在任何 header） |
| 登录过期：JWT exp + 可选 blacklist | token 固定 30 分钟（Redis TTL）+ refresh 续期 | 删 token 即时终止会话；refresh 校验账号状态 + 7 天窗口 |

## 验证模式对照

| 旧体系（装饰器决定） | 鉴权 | 新体系（请求形态决定） | 签名密钥 |
|---|---|---|---|
| `@Public()` | 无 | 第三方回调（唯一白名单） | 不签名（第三方自身验签手写在 controller） |
| 无装饰器（默认） | HMAC 签名 | 业务请求（带 `x-token`） | Redis 对象 secret |
| `@Login('<模块>')` | HMAC 签名 + JWT | 业务请求 + app scope | 同上 |
| — | — | login 入口（`@LoginEntry`，body 无 refreshToken） | bootstrap 初始密钥（固定密钥 + 时间窗口，两端独立计算） |
| — | — | refresh 入口（`@LoginEntry`，body 带 refreshToken） | refreshToken 派生密钥（`HMAC(refreshToken, 固定盐)`，能签名 == 持有 refreshToken） |

## 端点对照

| 旧体系 | token 体系 |
|--------|-----------|
| `POST sign/issue` → appKey + appSecret（存 api_keys 表） | 无（客户端用 bootstrap 初始密钥签名 login） |
| `POST sign/refresh` → 新 appSecret | 无（refreshToken 轮换内建于 login 链） |
| `POST login/login` → JWT | `POST {app}/login/login` → token + refreshToken + secret（`@LoginEntry`） |
| 无独立 refresh 端点 | `POST {app}/login/refresh` → 新 token + 新 refreshToken（body 上送 refreshToken，`@LoginEntry`） |

## 客户端对照

| 旧体系 | token 体系 |
|--------|-----------|
| `createCredentialFetcher` + `createApiClient`（先取 appKey 再签名） | token 会话客户端：login 后持有 token + secret 自动签名；token 过期**内部自动 refresh**（固定流程，不暴露公共 refresh 方法、不生成前端 refresh 调用函数） |
| 每次请求带 `x-app-key` + HMAC 签名 + `Authorization` | 每次请求带 `x-token` + 签名（secret 取自会话） |

## 迁移清单（旧项目 → token 体系）

1. **删 SignController / sign 链**：`sign/issue`、`sign/refresh` 端点、`schema/api_keys.table.ts`、sign-db/sign-redis-driver 接线全部移除。
2. **`@Public` 清理**：除 thirdApis 名下回调 controller 外一律删除（lint 白名单强制）。
3. **登录链重建**：`token_schema/{api}/{app}/token/*.token.ts` 定义身份（identity 投影身份表，硬约束三列）→ `pylonts gen token` → `login.config.ts` 引用 token → `pylonts gen login`。
4. **auth 链配置**：`AppConfig.auth.token`——`resolveToken`（Redis 还原）、`bootstrapSecrets`（app → 初始密钥）、`refreshKeySalt`（refreshToken 派生盐）、nonceStore。
5. **controller 第二参**：`(body, user: User)` → `(body, token: {Name}Token)`——`pylonts gen controller` 自动按 api+app 推导，app 无 token 声明会报错（补齐声明）。
6. **JWT 依赖清理**：`signToken` / `verifyToken` / blacklist / `Authorization` 解析全部移除。