# 模块认证规范 > 本文档定义了业务模块如何正确使用平台认证能力,确保所有 API 都正确携带和验证用户身份。 ## 1. 架构概览 ``` ┌─────────────────────────────────────────────────────────────┐ │ 前端 │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ common/api/axios.ts ← 带认证的 axios 实例 │ │ │ │ framework/auth/api.ts ← Token 管理 (getAccessToken)│ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ │ │ Authorization: Bearer ▼ ┌─────────────────────────────────────────────────────────────┐ │ 后端 │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ common/auth/auth.middleware.ts │ │ │ │ - authenticate: 验证 JWT Token │ │ │ │ - requirePermission: 权限检查 │ │ │ │ - requireRoles: 角色检查 │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ## 2. 前端规范 ### 2.1 使用带认证的 axios 实例(推荐) ```typescript // 导入带认证的 apiClient import apiClient from '../../../common/api/axios'; // 使用方式与 axios 完全相同,自动携带 JWT Token const response = await apiClient.get('/api/v2/xxx'); const response = await apiClient.post('/api/v2/xxx', data); ``` ### 2.2 使用原生 fetch(需手动添加 Token) ```typescript import { getAccessToken } from '../../../framework/auth/api'; // 创建 getAuthHeaders 函数 function getAuthHeaders(): HeadersInit { const headers: Record = { 'Content-Type': 'application/json', }; const token = getAccessToken(); if (token) { headers['Authorization'] = `Bearer ${token}`; } return headers; } // 所有 fetch 请求使用 getAuthHeaders() const response = await fetch(url, { headers: getAuthHeaders(), }); // 文件上传(不设置 Content-Type) const token = getAccessToken(); const headers: HeadersInit = {}; if (token) { headers['Authorization'] = `Bearer ${token}`; } const response = await fetch(url, { method: 'POST', headers, body: formData, }); ``` ## 3. 后端规范 ### 3.1 路由添加认证中间件 ```typescript // 导入认证中间件 import { authenticate, requirePermission } from '../../../common/auth/auth.middleware.js'; // 添加到路由 fastify.get('/xxx', { preHandler: [authenticate] }, handler); // 需要特定权限 fastify.post('/xxx', { preHandler: [authenticate, requirePermission('module:action')] }, handler); ``` ### 3.2 控制器获取用户 ID ```typescript /** * 获取用户ID(从JWT Token中获取) */ function getUserId(request: FastifyRequest): string { const userId = (request as any).user?.userId; if (!userId) { throw new Error('User not authenticated'); } return userId; } // 在控制器方法中使用 async function myHandler(request: FastifyRequest, reply: FastifyReply) { const userId = getUserId(request); // ... 使用 userId } ``` ### 3.3 JWT Token 结构 ```typescript interface DecodedToken { userId: string; // 用户ID phone: string; // 手机号 role: string; // 角色 tenantId: string; // 租户ID tenantCode?: string; // 租户Code iat: number; // 签发时间 exp: number; // 过期时间 } ``` ## 4. 检查清单 ### 4.1 新模块开发检查清单 - [ ] **前端 API 文件** - [ ] 使用 `apiClient` 或添加 `getAuthHeaders()` - [ ] 文件上传单独处理(不设置 Content-Type) - [ ] 导出函数不包含测试用 userId 参数 - [ ] **后端路由文件** - [ ] 导入 `authenticate` 中间件 - [ ] 所有需要认证的路由添加 `preHandler: [authenticate]` - [ ] 公开 API(如模板列表)可不添加认证 - [ ] **后端控制器文件** - [ ] 添加 `getUserId()` 辅助函数 - [ ] 移除所有 `MOCK_USER_ID` 或硬编码默认值 - [ ] 使用 `getUserId(request)` 获取用户 ID ### 4.2 已完成模块状态 | 模块 | 前端 API | 后端路由 | 后端控制器 | 状态 | |------|---------|---------|-----------|------| | RVW | ✅ apiClient | ✅ authenticate | ✅ getUserId | ✅ | | PKB | ✅ 拦截器 | ✅ authenticate | ✅ getUserId | ✅ | | ASL | ✅ getAuthHeaders | ✅ authenticate | ✅ getUserId | ✅ | | DC Tool B | ✅ getAuthHeaders | ✅ authenticate | ✅ getUserId | ✅ | | DC Tool C | ✅ apiClient | ✅ authenticate | ✅ getUserId | ✅ | | IIT | N/A (企业微信) | N/A | ✅ 企业微信userId | ✅ | | Prompt管理 | ✅ getAuthHeaders | ✅ authenticate | ✅ getUserId | ✅ | ## 5. 常见错误和解决方案 ### 5.1 401 Unauthorized **原因**: 前端没有携带 JWT Token 或 Token 过期 **解决**: 1. 检查前端 API 是否使用 `apiClient` 或 `getAuthHeaders()` 2. 检查 localStorage 中是否有 `accessToken` 3. 如果 Token 过期,尝试刷新或重新登录 ### 5.2 User not authenticated **原因**: 后端路由没有添加 `authenticate` 中间件 **解决**: 在路由定义中添加 `preHandler: [authenticate]` ### 5.3 TypeError: Cannot read property 'userId' of undefined **原因**: 使用了错误的属性名(`request.user.id` 而非 `request.user.userId`) **解决**: 使用 `(request as any).user?.userId` ## 6. 参考文件 - 前端 axios 实例: `frontend-v2/src/common/api/axios.ts` - 前端 Token 管理: `frontend-v2/src/framework/auth/api.ts` - 后端认证中间件: `backend/src/common/auth/auth.middleware.ts` - 后端 JWT 服务: `backend/src/common/auth/jwt.service.ts`