# 代码实现审计报告 — PRD vs 实际实现 **审计日期:** 2026-05-26 **审计范围:** - PRD 文档: `chuangfei-platform-product-modules.md` (M1-M11), `FRONTEND_UI_SPECIFICATION.md`, `chuangfei-platform-bpm-and-roadmap.md` - 后端: `services/delivery-platform-api/` (153 Java 文件) + `services/license-webhook-ingress/` - 前端: `web/delivery-platform-ui/src/` (47 源文件) --- ## 1. 严重缺陷 (Critical) ### CR-01: 认证系统硬编码用户凭据 **位置:** `AuthController.java:54-89` **PRD 对照:** M11-F14 要求「用户与账号生命周期:创建、启用/禁用、离职归档」 **实际实现:** 4 个用户硬编码在 Java switch 语句中: ```java case "admin" → role=SYS_ADMIN case "sales" → role=SALES case "delivery" → role=DELIVERY case "ops" → role=LICENSE_OPS ``` **缺陷:** - 密码 = 小写用户名 (`pass.equals(user.toLowerCase())`) — admin/admin, sales/sales - 无数据库用户表 — 无法 CRUD、禁用、归档 - 注入的 `PasswordEncoder` (BCrypt) 仅用于 `changePassword` 端点,登录完全不使用 - `changePassword` 验证旧密码时硬编码 `passwordEncoder.encode("admin")`,对非 admin 用户永远失败 **影响:** 无法管理用户、密码与用户名相同、安全基线不达标 --- ### CR-02: JWT Token 存储在 localStorage **位置:** `stores/auth.js:4,8,29,37` **PRD 对照:** §16.6 已知局限明确标注「前端 Token 存 localStorage(非 HttpOnly Cookie)」为已知安全缺陷,计划 Mid 迁移 **实际实现:** Token 通过 `localStorage.setItem(TOKEN_KEY)` 持久化 ```javascript // auth.js:29 localStorage.setItem(TOKEN_KEY, this.token); axios.defaults.headers.common.Authorization = `Bearer ${this.token}`; ``` **缺陷:** XSS 攻击可窃取 localStorage 中的 JWT,获得完整 API 访问权限 **影响:** 安全 — XSS 窃取 → 权限丢失 **缓解:** 当前通过 CSP + 前端无富文本渲染降低风险,但未根本解决 --- ### CR-03: Error Message 泄露 **位置:** - `LicenseController.java:25` — `ResponseEntity.internalServerError().body(Map.of("error", e.getMessage()))` - `ContractController.java:136` — `ResponseEntity.status(500).body(Map.of("error", e.getMessage()))` **PRD 对照:** 无明确的错误消息规范,但全局 `ApiExceptionHandler` 已返回泛化消息 "服务器内部错误" **缺陷:** 这两个 Controller 使用 try-catch 捕获 `Exception` 并将异常消息原文 (`e.getMessage()`) 返回给客户端,绕过了全局异常处理器。可能泄露实现细节(表名、SQL、文件路径)。 **影响:** 信息安全 — 生产环境可能泄露堆栈或内部路径信息 --- ### CR-04: resetPassword 和 forceLogout 是空操作 **位置:** `AuthController.java:131-148` **PRD 对照:** M11-F08「密码重置: 管理员重置密码或邮件/短信重置链接」、M11-F12「管理员强制下线」 **实际实现:** 两个端点均仅有参数校验,无实际逻辑 ```java // resetPassword — 校验参数后直接返回 200 OK,未更新任何密码 @PostMapping("/admin/reset-password") public ResponseEntity resetPassword(@RequestBody Map body) { String username = body.get("username"); String newPassword = body.get("newPassword"); if (username == null || newPassword == null || newPassword.length() < 6) { throw new ResponseStatusException(...); } return ResponseEntity.ok().build(); // 没有实际更新密码! } // forceLogout — 同上,无会话失效逻辑 @PostMapping("/admin/force-logout") public ResponseEntity forceLogout(@RequestBody Map body) { String username = body.get("username"); if (username == null) throw new ResponseStatusException(...); return ResponseEntity.ok().build(); // 没有实际使会话失效! } ``` **影响:** 功能完全不可用 — 前端调用后显示成功,实际无效果 --- ## 2. 高危缺陷 (High) ### HI-01: 无用户管理数据库表 **位置:** `AuthController.java` (全部) **PRD 对照:** M11-F14「用户与账号生命周期:创建、启用/禁用、离职归档」— P0 **实际:** 无 `platform_user` 表或类似实体。4 个用户硬编码。Flyway 迁移 V15 `seed_product_roles.sql` 仅涉及角色种子数据。 **影响:** M11-F14 完全未实现,无法添加/禁用/管理用户 --- ### HI-02: 权限模型硬编码 **位置:** `AuthController.java:91-114` **PRD 对照:** §13.4 要求权限码命名规范(如 `customer:project:rw`、`contract:order:export`) **实际:** 权限字符串在 Java switch 中硬编码,非数据库驱动、不可配置 ```java case "SALES": permissions.add("customer:*"); permissions.add("project:*"); permissions.add("contract:*"); permissions.add("delivery:read"); break; ``` **影响:** 新增角色或调整权限需改代码重启;权限码 `v-permission` 指令在前端存在但后端无对应校验 --- ### HI-03: 会话管理后端无状态 **位置:** `SecurityConfig.java:55-56` — `SessionCreationPolicy.STATELESS` **PRD 对照:** M11-F03「空闲超时自动登出」、M11-F11「并发会话策略」 **实际:** JWT 无状态设计意味着后端无法主动使会话失效(无会话存储)。空闲超时仅在前端实现(`idleTimer.js`),后端无法强制登出。`forceLogout` API 为空操作。 **影响:** 并发会话、强制下线、空闲超时功能均无法在后端层面实现 --- ### HI-04: LicenseController 异常处理绕过全局 Handler **位置:** `LicenseController.java:19-27` **PRD 对照:** 全局 `ApiExceptionHandler` 已提供统一错误格式 `{status, message}` **实际:** `create` 方法手动 try-catch,返回非标准错误格式 ```java try { return ResponseEntity.ok(licenseService.create(request)); } catch (Exception e) { return ResponseEntity.internalServerError().body(Map.of("error", e.getMessage())); } ``` 返回格式为 `{"error": "..."}` 而非全局标准的 `{"status": 500, "message": "..."}` **影响:** API 响应格式不一致,前端 `apiErrorMessage.js` 可能无法解析 --- ## 3. 中危缺陷 (Medium) ### ME-01: 合同附件上传无校验 **位置:** `ContractController.java:118-137` **PRD 对照:** M2-F05「合同附件:上传扫描件/电子签输出(存储与权限受控)」 **实际:** 上传端点无文件大小限制、无文件类型白名单、无病毒扫描 ```java @PostMapping("/{id}/attachments") public ResponseEntity> uploadAttachment(@PathVariable Long id, @RequestParam("file") MultipartFile file) { // 无 file.getSize() 校验 // 无 file.getContentType() 白名单 // 直接将文件写入本地磁盘 ``` **影响:** 可能被用于上传恶意文件;磁盘可能被大文件填满 --- ### ME-02: 部分 Controller 返回格式不统一 **位置:** 多文件 **PRD 对照:** 无明确 API 响应规范 **实际:** 存在三种返回风格: 1. 全局 `ApiExceptionHandler` → `{status, message}` (标准) 2. `LicenseController` → `{error, ...}` (非标准) 3. `ContractController` → `{error, ...}` (非标准) 4. 部分端点直接返回实体对象(非 Map) **影响:** 前端 `apiErrorMessage.js` 兼容多种格式但无法覆盖所有情况 --- ### ME-03: 系统参数仅存于 localStorage **位置:** `SystemParamsView.vue:14-33` **PRD 对照:** M11-F20「系统参数」— 期望持久化到后端数据库 **实际:** ```javascript // 直接保存到浏览器 localStorage localStorage.setItem('systemParams', JSON.stringify(params.value)) ElMessage.success('参数已保存(MVP: 存储于浏览器本地)') ``` **影响:** 参数仅对当前浏览器有效,不同用户/设备参数不一致;清除浏览器数据后丢失 --- ### ME-04: 后端 changePassword 验证逻辑错误 **位置:** `AuthController.java:151-168` **PRD 对照:** M11-F07「已登录用户修改本人密码;校验旧密码强度与新密码策略」 **实际:** ```java String currentPasswordHash = passwordEncoder.encode("admin"); // 始终比较 admin 的密码! if (!passwordEncoder.matches(oldPassword, currentPasswordHash)) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "旧密码错误"); } ``` `passwordEncoder.encode("admin")` 硬编码为 "admin",导致: - admin 用户可以改密(旧密码 = admin 通过) - 其他用户(sales/delivery/ops)永远无法通过旧密码验证 --- ### ME-05: SN 批量导入缺少事务回滚 **位置:** `LicenseSnService.java:134-155` **PRD 对照:** M4-F01/F07 批量操作 **实际:** 逐条插入,失败项跳过继续,但方法未标注 `@Transactional` ```java public Map batchImport(List requests) { // for 循环逐条 insert,无事务保护 // 部分成功 = 部分写入无法回滚 ``` **影响:** 批量导入 100 条中第 50 条失败时,前 49 条已写入无法撤销 --- ## 4. PRD 与实现偏离 (Misalignment) ### MA-01: M11 角色模型偏离产品定义 | 产品定义角色 | 实际实现 | 状态 | |------------|---------|------| | SYS_ADMIN | ✅ | 产品定义包含 | | SALES | ✅ I10 新增 | 产品定义包含 | | DELIVERY | ✅ I10 新增 | 产品定义包含 | | LICENSE_OPS | ✅ I10 新增 | 产品定义包含 | | ORDER_SUPPORT | ○ | 产品定义但未实现 | | FINANCE_VIEW | ○ | 产品定义但未实现 | | COMPLIANCE | ○ | 产品定义但未实现 | | EXEC_VIEW | ○ | 产品定义但未实现 | | SECURITY_ADMIN | ○ | 产品定义但未实现 | | DEVELOPER | ✅ (应废弃) | MVP 遗留非标角色 | | OPS | ✅ (应废弃) | MVP 遗留非标角色 | 前端路由角色标记(`router/index.js`)仍广泛使用 `SYS_ADMIN` 和 `SALES`,但 `DEVELOPER` 已从路由角色列表中移除,而 `LICENSE_OPS` 和 `DELIVERY` 已加入。 ### MA-02: M1-F07 客户冻结后端就绪前端缺 UI **位置:** 后端 `CustomerController.java` 有 `PATCH /{id}/freeze` 和 `/unfreeze` 端点,前端 `CustomersView.vue` 无冻结操作入口 ### MA-03: M11-F07 密码修改已实现但产品文档未标记 `ProfileView.vue` 已包含完整的改密弹窗,`AuthController` 有对应端点,但文档标注为 ○。 --- ## 5. 代码质量问题 (Code Quality) ### CQ-01: SecurityConfig 重复 import (已修复) **位置:** `SecurityConfig.java:5,20` — 两次 `import org.springframework.context.annotation.Bean` **状态:** ✅ 本次审计已修复 ### CQ-02: 个别 Controller 使用 `@PreAuthorize` 而非 JWT Filter 角色 **位置:** `LicenseController.java:20,30,40` — 使用 `@PreAuthorize("hasRole('LICENSE_OPS') or hasRole('ADMIN')")` **问题:** 与 JWT Filter 的双重验证增加了 role 前缀处理的复杂性(JwtAuthFilter 添加 `ROLE_` 前缀,`@PreAuthorize` 期望 `ROLE_` 格式) ### CQ-03: 前端 API 层中个别函数含 query 参数拼接 **位置:** `platform.js:460` ```javascript export function createSkuMapping(contractLineId, body) { return axios.post(`/api/v1/integration/sku-mappings?contractLineId=${contractLineId}`, body); } ``` **影响:** 非紧急,但建议统一使用 `{ params: { contractLineId } }` 方式 --- ## 6. 未被 PRD 覆盖但代码已实现的模块(超前实现) | 模块 | 功能 | 建议 | |------|------|------| | M7 设备管理 | 登记/列表/详情/绑定/换机申请 | 核对 PRD 需求后决定是否纳入正式范围 | | M8 通知待办 | 待办中心 + 通知通道配置 UI | 需补充实际发送逻辑 | | M9 报表对账 | 4 个报表页面均已上线 | 补充导出按钮和推送逻辑 | | M6 ID/特征/SKU 映射 | 前后端均已实现 | 更新产品文档状态 | --- ## 7. 汇总统计 | 严重级别 | 数量 | 编号 | |---------|------|------| | 🔴 Critical | 4 | CR-01~CR-04 | | 🟠 High | 4 | HI-01~HI-04 | | 🟡 Medium | 5 | ME-01~ME-05 | | 🔵 Misalignment | 3 | MA-01~MA-03 | | ⚪ Code Quality | 3 | CQ-01~CQ-03 | | **合计** | **19** | | --- ## 8. 修复建议优先级 ### P0 — 立即修复(安全基线) 1. **CR-01** (硬编码用户): 创建 `platform_user` 表 + Flyway 迁移 + AuthController 改用数据库查询 + BCrypt 密码校验 2. **CR-04** (空操作端点): `resetPassword` 和 `forceLogout` 补充实际逻辑 — resetPassword 更新用户密码,forceLogout 增加黑名单机制 3. **CR-03** (错误泄露): `LicenseController` 和 `ContractController` 移除 try-catch,让全局 `ApiExceptionHandler` 接管 4. **ME-04** (改密逻辑错误): `changePassword` 从 SecurityContext 获取当前用户名,从数据库查询对应用户的密码哈希 ### P1 — 短期修复 5. **HI-01** (用户管理): 在 P0 用户表基础上实现用户 CRUD API + 前端管理页面 6. **ME-01** (附件校验): ContractController upload 增加 `@Size` 注解和文件类型白名单 7. **ME-05** (事务缺失): `batchImport` 方法添加 `@Transactional` ### P2 — 中长期 8. **CR-02** (Token 存储): 迁移至 HttpOnly Cookie(需后端配合返回 Set-Cookie header) 9. **HI-02** (权限模型): 权限码持久化到数据库,实现可配置 RBAC 10. **HI-03** (会话管理): 引入 Token 黑名单/白名单机制或 Redis 会话存储