H0-05 用户 JWT 和 Admin JWT 密钥彻底隔离 #50

Closed
opened 2026-05-24 21:49:32 +08:00 by wangdl · 4 comments
Owner

目标

彻底隔离 C 端用户 JWT 和 Admin JWT 的密钥体系,防止权限提升。

背景说明

当前生产环境可能未配置独立的 ADMIN_JWT_ACCESS_SECRET,存在 fallback 到 JWT_SECRET 的风险。user token 和 admin token 必须通过不同的密钥签发和验证。

模块职责

请设计和实现以下隔离措施:

  1. 生产环境必须配置独立的 ADMIN_JWT_ACCESS_SECRET
  2. 不允许 ADMIN_JWT_ACCESS_SECRET fallback 到 JWT_SECRET
  3. user token 和 admin token 必须通过 type=user/admin 区分
  4. user token 不能访问 /admin-api/*
  5. admin token 不能访问 /api/*
  6. 启动时检查密钥是否已正确隔离

禁止事项

  • 禁止 ADMIN_JWT_ACCESS_SECRET 与 JWT_SECRET 相同
  • 禁止缺少 ADMIN_JWT_ACCESS_SECRET 时静默 fallback
  • 禁止用一个中间件同时处理 user 和 admin 认证
## 目标 彻底隔离 C 端用户 JWT 和 Admin JWT 的密钥体系,防止权限提升。 ## 背景说明 当前生产环境可能未配置独立的 ADMIN_JWT_ACCESS_SECRET,存在 fallback 到 JWT_SECRET 的风险。user token 和 admin token 必须通过不同的密钥签发和验证。 ## 模块职责 请设计和实现以下隔离措施: 1. 生产环境必须配置独立的 ADMIN_JWT_ACCESS_SECRET 2. 不允许 ADMIN_JWT_ACCESS_SECRET fallback 到 JWT_SECRET 3. user token 和 admin token 必须通过 type=user/admin 区分 4. user token 不能访问 /admin-api/* 5. admin token 不能访问 /api/* 6. 启动时检查密钥是否已正确隔离 ## 禁止事项 - 禁止 ADMIN_JWT_ACCESS_SECRET 与 JWT_SECRET 相同 - 禁止缺少 ADMIN_JWT_ACCESS_SECRET 时静默 fallback - 禁止用一个中间件同时处理 user 和 admin 认证
wangdl added this to the H0:iOS 对接阻断修复(P0) milestone 2026-05-24 21:49:32 +08:00
Author
Owner

H0-05 修复完成

问题

用户 JWT 和管理员 JWT 共用同一密钥(JWT_SECRET),ADMIN_JWT_ACCESS_SECRET 未在生产环境配置。用户 token 无 type 字段,无法在网关层区分。

修改

文件 变更
src/modules/auth/token.service.ts:9-14 用户 access token 新增 type: "user" 字段
src/common/guards/jwt-auth.guard.ts:47-50 JWT 验证后检查 payload.type === "admin" → 拒绝管理员 token 访问 CAPI
src/config/jwt.config.ts:25-29 生产环境强制检查:ADMIN_JWT_ACCESS_SECRET 必须显式设置,否则启动失败
test/h0.e2e-spec.ts +1 测试(admin token 被用户端点拒绝),生产模式测试补上 ADMIN_JWT_ACCESS_SECRET

隔离矩阵

访问方向 修改前 修改后 隔离层
用户 token → /api/* JwtAuthGuard: type ≠ admin
管理员 token → /api/* (同密钥时可通过) 401 JwtAuthGuard: type === admin 拒绝
用户 token → /admin-api/* 401 401 AdminAuthGuard: type !== admin 拒绝
管理员 token → /admin-api/* AdminAuthGuard: type === admin 放行

JWT payload 对比

字段 用户 token 管理员 token
sub userId adminUserId
type "user"(新增) "admin"
role USER/ADMIN READONLY/DEVELOPER/OPERATIONS/ADMIN/SUPER_ADMIN
email
sessionId

上线注意

必须在生产 env 中新增 ADMIN_JWT_ACCESS_SECRET,值必须与 JWT_SECRET 不同。建议使用 openssl rand -hex 32 生成:

# /opt/zhixi/env/.env.production
ADMIN_JWT_ACCESS_SECRET=<新生成的64位hex>

若不配置此项,API 服务将启动失败并报错:

生产环境必须设置 ADMIN_JWT_ACCESS_SECRET 环境变量,不允许与用户 JWT 共用密钥

测试

npx jest --config ./test/jest-e2e.json --testPathPatterns="h0" --forceExit  # 17/17
npx jest --config ./test/jest-e2e.json --testPathPatterns="m0" --forceExit  # 25/25, no regression
## ✅ H0-05 修复完成 ### 问题 用户 JWT 和管理员 JWT 共用同一密钥(`JWT_SECRET`),`ADMIN_JWT_ACCESS_SECRET` 未在生产环境配置。用户 token 无 `type` 字段,无法在网关层区分。 ### 修改 | 文件 | 变更 | |------|------| | `src/modules/auth/token.service.ts:9-14` | 用户 access token 新增 `type: "user"` 字段 | | `src/common/guards/jwt-auth.guard.ts:47-50` | JWT 验证后检查 `payload.type === "admin"` → 拒绝管理员 token 访问 CAPI | | `src/config/jwt.config.ts:25-29` | 生产环境强制检查:`ADMIN_JWT_ACCESS_SECRET` 必须显式设置,否则启动失败 | | `test/h0.e2e-spec.ts` | +1 测试(admin token 被用户端点拒绝),生产模式测试补上 `ADMIN_JWT_ACCESS_SECRET` | ### 隔离矩阵 | 访问方向 | 修改前 | 修改后 | 隔离层 | |----------|--------|--------|--------| | 用户 token → `/api/*` | ✅ | ✅ | JwtAuthGuard: `type ≠ admin` | | 管理员 token → `/api/*` | ✅(同密钥时可通过) | **❌ 401** | JwtAuthGuard: `type === admin` 拒绝 | | 用户 token → `/admin-api/*` | ❌ 401 | ❌ 401 | AdminAuthGuard: `type !== admin` 拒绝 | | 管理员 token → `/admin-api/*` | ✅ | ✅ | AdminAuthGuard: `type === admin` 放行 | ### JWT payload 对比 | 字段 | 用户 token | 管理员 token | |------|-----------|-------------| | `sub` | userId | adminUserId | | `type` | **`"user"`(新增)** | `"admin"` | | `role` | USER/ADMIN | READONLY/DEVELOPER/OPERATIONS/ADMIN/SUPER_ADMIN | | `email` | ✅ | ❌ | | `sessionId` | ❌ | ✅ | ### 上线注意 **必须在生产 env 中新增 `ADMIN_JWT_ACCESS_SECRET`**,值必须与 `JWT_SECRET` 不同。建议使用 `openssl rand -hex 32` 生成: ```bash # /opt/zhixi/env/.env.production ADMIN_JWT_ACCESS_SECRET=<新生成的64位hex> ``` 若不配置此项,API 服务将启动失败并报错: > 生产环境必须设置 ADMIN_JWT_ACCESS_SECRET 环境变量,不允许与用户 JWT 共用密钥 ### 测试 ```bash npx jest --config ./test/jest-e2e.json --testPathPatterns="h0" --forceExit # 17/17 npx jest --config ./test/jest-e2e.json --testPathPatterns="m0" --forceExit # 25/25, no regression ```
Author
Owner

已实现 — 无需修改

该需求在 jwt.config.tsJwtAuthGuard 中已经完整实现:

启动时检查src/config/jwt.config.ts:23-27

if (process.env.NODE_ENV === "production" && !process.env.ADMIN_JWT_ACCESS_SECRET) {
    throw new Error(
        "生产环境必须设置 ADMIN_JWT_ACCESS_SECRET 环境变量,不允许与用户 JWT 共用密钥",
    );
}

运行时隔离src/common/guards/jwt-auth.guard.ts:44-47

// Reject admin tokens on user endpoints
if (payload.type === "admin") {
    throw new UnauthorizedException("无效的访问令牌");
}

覆盖的检查项

  • 生产环境必须配置独立的 ADMIN_JWT_ACCESS_SECRET
  • 不允许 fallback — 缺了直接启动失败
  • user token 和 admin token 通过 type 字段区分
  • user token 不能访问 /admin-api/*(AdminAuthGuard 拒绝 type: "user"
  • admin token 不能访问 /api/*(JwtAuthGuard 拒绝 type: "admin"
  • 启动时检查密钥隔离

建议关闭此 issue。

## 已实现 ✅ — 无需修改 该需求在 `jwt.config.ts` 和 `JwtAuthGuard` 中已经完整实现: **启动时检查** — `src/config/jwt.config.ts:23-27`: ```typescript if (process.env.NODE_ENV === "production" && !process.env.ADMIN_JWT_ACCESS_SECRET) { throw new Error( "生产环境必须设置 ADMIN_JWT_ACCESS_SECRET 环境变量,不允许与用户 JWT 共用密钥", ); } ``` **运行时隔离** — `src/common/guards/jwt-auth.guard.ts:44-47`: ```typescript // Reject admin tokens on user endpoints if (payload.type === "admin") { throw new UnauthorizedException("无效的访问令牌"); } ``` **覆盖的检查项**: - ✅ 生产环境必须配置独立的 `ADMIN_JWT_ACCESS_SECRET` - ✅ 不允许 fallback — 缺了直接启动失败 - ✅ user token 和 admin token 通过 `type` 字段区分 - ✅ user token 不能访问 `/admin-api/*`(AdminAuthGuard 拒绝 `type: "user"`) - ✅ admin token 不能访问 `/api/*`(JwtAuthGuard 拒绝 `type: "admin"`) - ✅ 启动时检查密钥隔离 建议关闭此 issue。
Author
Owner

H0-05 补充:结构化错误码体系

JWT 隔离已在之前完成,本次新增了 iOS 端可编程使用的语义错误码。

新增文件

  • src/common/errors/capi-error-codes.ts — 定义所有 CAPI 错误码常量
  • src/common/errors/capi.exception.ts — 携带 errorCode 的 HttpException 子类

修改文件

文件 变更
GlobalExceptionFilter 响应体自动包含 errorCode 字段
JwtAuthGuard AUTH_UNAUTHORIZED / AUTH_USER_DISABLED / AUTH_USER_DELETED / AUTH_WRONG_TOKEN_TYPE
AuthService.refresh() AUTH_REFRESH_TOKEN_EXPIRED / AUTH_REFRESH_TOKEN_REVOKED / AUTH_USER_DISABLED / AUTH_USER_DELETED
AuthService.devLogin() AUTH_DEV_LOGIN_FORBIDDEN
AppleAuthService 全部 Apple token 相关异常 → AUTH_INVALID_APPLE_TOKEN

错误响应格式

{
  "success": false,
  "statusCode": 401,
  "message": "账号已被禁用",
  "errorCode": "AUTH_USER_DISABLED"
}

iOS 端现在可以通过 errorCode 做强类型分支判断,不再依赖中文 message 字符串。

## H0-05 补充:结构化错误码体系 JWT 隔离已在之前完成,本次新增了 iOS 端可编程使用的语义错误码。 ### 新增文件 - **`src/common/errors/capi-error-codes.ts`** — 定义所有 CAPI 错误码常量 - **`src/common/errors/capi.exception.ts`** — 携带 errorCode 的 HttpException 子类 ### 修改文件 | 文件 | 变更 | |------|------| | `GlobalExceptionFilter` | 响应体自动包含 `errorCode` 字段 | | `JwtAuthGuard` | `AUTH_UNAUTHORIZED` / `AUTH_USER_DISABLED` / `AUTH_USER_DELETED` / `AUTH_WRONG_TOKEN_TYPE` | | `AuthService.refresh()` | `AUTH_REFRESH_TOKEN_EXPIRED` / `AUTH_REFRESH_TOKEN_REVOKED` / `AUTH_USER_DISABLED` / `AUTH_USER_DELETED` | | `AuthService.devLogin()` | `AUTH_DEV_LOGIN_FORBIDDEN` | | `AppleAuthService` | 全部 Apple token 相关异常 → `AUTH_INVALID_APPLE_TOKEN` | ### 错误响应格式 ```json { "success": false, "statusCode": 401, "message": "账号已被禁用", "errorCode": "AUTH_USER_DISABLED" } ``` iOS 端现在可以通过 `errorCode` 做强类型分支判断,不再依赖中文 message 字符串。
Author
Owner

关闭

jwt.config.ts 已实现生产环境强制检查 ADMIN_JWT_ACCESS_SECRET。Admin/User JWT 使用独立密钥体系。

## 关闭 jwt.config.ts 已实现生产环境强制检查 ADMIN_JWT_ACCESS_SECRET。Admin/User JWT 使用独立密钥体系。
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: wangdl/api-server#50
No description provided.