文档审计:归档试用前第二轮业务操作完整性与直接路由权限审计报告

This commit is contained in:
wangzhibiao committed 2026-09-20 12:31:42 +08:00
1 parent 2a7eacf539
commit 5bcb6273ef
1 file changed
+120
@@ -0,0 +1,120 @@
# 试用前全系统缺陷清扫第二轮:业务操作完整性与直接路由权限审计报告
- **报告日期**:2026-09-20
- **基线分支**:`fix/pre-customer-stabilization-20260920`
- **目标环境**:共享试用环境(`http://192.168.139.230:8082` / 本地测试隧道 `http://127.0.0.1:8082`)
- **审计重点**:
1. 16 个业务实体的完整生命周期与破坏性操作(DELETE / DISABLE / CANCEL / VOID / REVOKE / UNBIND)审计;
2. 隐藏路由直接 URL 访问防御与前后端权限真实性审计;
3. 头像 404 彻底清零与兜底保障。
---
## 一、16 个业务实体生命周期审计矩阵
| 序号 | 实体对象 (ENTITY) | 当前UI操作 (CURRENT_UI_ACTION) | 当前对应API (CURRENT_API) | 真实操作类型 (REAL_OPERATION_TYPE) | 是否可安全物理删除 (CAN_SAFELY_DELETE) | 下游拦截与级联防护机制 (DOWNSTREAM_GUARD) | 缺失操作 (MISSING_OPERATION) | 建议与处置动作 (RECOMMENDED_ACTION) |
| :---: | :--- | :--- | :--- | :---: | :---: | :--- | :---: | :--- |
| 1 | **教师 (Teacher)** | 查询、新增、修改、重置密码、修改状态、解除账号绑定 | `GET/POST/PUT /train/teacher`, `DELETE /train/account-bindings/teacher/{tId}` | `DISABLE` / `UNBIND` | **NO** | 后端未暴露物理删除接口;教师关联授课课次及历史排课,物理删除会导致历史排课课次缺失授课教师 | 无 | 保持现状(`NO_DELETE_ALLOWED`),严禁新增物理删除 |
| 2 | **学员 (Student)** | 查询、新增、修改、重置密码、解除账号绑定 | `GET/POST/PUT /train/student`, `DELETE /train/account-bindings/student/{sId}` | `UNBIND` | **NO** | 后端未暴露物理删除接口;学员关联参训、报到、成绩、住宿、门禁等履约核心数据,不可物理删除 | 无 | 保持现状(`NO_DELETE_ALLOWED`),严禁新增物理删除 |
| 3 | **课程 (Course)** | 查询、新增、修改 | `GET/POST /train/course`, `PUT /train/course/{cId}` | `NO_DELETE_ALLOWED` | **NO** | 后端未暴露物理删除接口;课程被开班计划、课次日程、成绩档案多方关联 | 无 | 保持现状(`NO_DELETE_ALLOWED`) |
| 4 | **教室/场地 (Classroom)** | 查询、新增、修改 | `GET/POST /train/classroom`, `PUT /train/classroom/{classRoomId}` | `NO_DELETE_ALLOWED` | **NO** | 后端未暴露物理删除接口;教学设施与教室被各课次时间段排课关联 | 无 | 保持现状(`NO_DELETE_ALLOWED`) |
| 5 | **宿舍资源 (Dormitory)** | 查询、新增、房间详情 | `GET/POST /train/dormitory`, `GET /train/dormitory/options` | `NO_DELETE_ALLOWED` | **NO** | 官方契约 V1.0.2 冻结仅 4 个端点,未暴露物理删除;房间关联床位分配、门锁映射及授权记录 | 无 | 保持现状(`NO_DELETE_ALLOWED`) |
| 6 | **培训计划 (Training Plan)** | 查询、新增、修改、结训 | `GET/POST/PUT /train/plans`, `POST /train/plans/{planId}/complete` | `CANCEL_VOID` | **NO** | 核心业务聚合根;无物理删除接口,生命周期终态通过 `completePlan` 完成结训流转与学员状态联动归档 | 无 | 保持现状(`NO_DELETE_ALLOWED` / 终态归档) |
| 7 | **计划课程 (Plan Course)** | 配置添加、移除配置 | `GET/POST /train/plans/{planId}/courses`, `DELETE /train/plans/{planId}/courses/{id}` | `PHYSICAL_DELETE` | **YES_WITH_GUARD** | `PlanAppServiceImpl.removePlanCourse` 严格三重校验:计划锁定校验、课次关联校验、成绩项关联校验、自主学习记录关联校验,存在任何业务关联严禁删除 | 无 | 保持现状,现有依赖拦截保护完备 |
| 8 | **课次 (Lesson)** | 新增排课、修改课次、删除课次、排课冲突预检 | `POST/PUT /train/lessons`, `DELETE /train/lessons/{lessonId}` | `PHYSICAL_DELETE` | **YES_WITH_GUARD** | `SchedulingAppServiceImpl.deleteLesson` 严格前置校验:`countAttendanceByLessonId(lessonId) > 0` 强力阻断,已有打卡记录严禁删除 | 无 | 保持现状,现有考勤打卡依赖保护完备 |
| 9 | **参训记录 (Enrollment)** | 名单导入/新增、变更状态(放弃/退训)、移除人员 | `PUT /train/plans/enrollments/{id}/status`, `DELETE /train/plans/enrollments/{id}` | `CANCEL_VOID` / `PHYSICAL_DELETE` | **YES_WITH_GUARD** | `EnrollmentAppServiceImpl.removeEnrollment` 严格五重业务校验:计划结训锁定、考勤记录、成绩记录、报到记录、住宿记录、学习时长;产生任何业务数据禁止物理删除,必须转为退训状态 | 无 | 保持现状,现有五重关联依赖保护完备 |
| 10 | **报到记录 (Report)** | 查询列表、学员报到确认、报到导出 | `GET /train/report/list`, `POST /train/enrollments/{id}/report` | `NO_DELETE_ALLOWED` | **NO** | 报到是学员到校履约的关键事实证据,系统未暴露物理删除接口,不可撤销删除 | 无 | 保持现状(`NO_DELETE_ALLOWED`) |
| 11 | **考勤记录 (Attendance)** | 查询考勤签到、导出签到数据 | `GET /train/checkin/list`, `POST /train/checkin/export` | `NO_DELETE_ALLOWED` | **NO** | 原始课次打卡流水是学时与考核依据,系统未暴露物理删除接口 | 无 | 保持现状(`NO_DELETE_ALLOWED`) |
| 12 | **自主学习记录 (Self-study)** | 查询学习时长统计、导出学习记录 | `GET /train/duration/list`, `POST /train/duration/export` | `NO_DELETE_ALLOWED` | **NO** | 线上自主学时统计流水是结训资格判定的法定数据源,系统未暴露物理删除接口 | 无 | 保持现状(`NO_DELETE_ALLOWED`) |
| 13 | **住宿记录 (Accommodation)** | 入住分配、退宿、调房、删除已退宿记录 | `POST /train/accommodation/checkin/checkout/transfer`, `DELETE /train/accommodation/{ids}` | `CANCEL_VOID` / `PHYSICAL_DELETE` | **YES_WITH_GUARD** | `TrainAccommodationServiceImpl.deleteTrainAccommodationByAccommodationId` 严格前置拦截:学员处于在住状态(CHECKED_IN)严禁物理删除,必须先办理退宿 | 无 | 保持现状,现有在住状态防护完备 |
| 14 | **DMS资源映射 (DMS mapping)** | 查询映射、绑定房间/门锁映射、解除映射 | `GET/POST /train/access/mapping/*`, `DELETE /train/access/mapping/{id}` | `DISABLE` / `UNBIND` | **NO** | `deactivateMapping` 实现为软注销/逻辑失效(将 `mapping_status` 置为失效),不物理删除映射数据行,保留审计追溯链 | 无 | 保持现状(逻辑注销停用) |
| 15 | **门禁授权 (Door auth)** | 查询授权、流水审计、手动对账、注销授权 | `GET/POST /train/auth/*`, `POST /train/auth/{id}/revoke`, `DELETE /train/auth/{ids}` | `REVOKE` / `PHYSICAL_DELETE` | **YES_WITH_GUARD** | **本轮加固**:`TrainDormAuthServiceImpl.deleteTrainDormAuthByAuthId` 增加状态拦截,处于生效中(ACTIVE)或授权中(GRANTING)的门禁记录严禁物理删除,必须先执行注销(REVOKE),彻底杜绝物理授权与系统脱节风险 | 无 | 本轮已完成状态拦截与单元测试加固 |
| 16 | **成绩相关对象 (Scores)** | 查询成绩、新增成绩、修改成绩、删除成绩 | `GET/POST/PUT/DELETE /train/scoreinfo/*` | `PHYSICAL_DELETE` | **YES_WITH_GUARD** | 单表录入期纠错删除;**本轮加固**:在前端 `views/train/score/index.vue` 按钮补充 `v-hasPermi="['system:info:remove']"` 等权限指令,严格对齐后端 RBAC | 无 | 本轮已完成前端按钮权限指令对齐加固 |
---
## 二、直接 URL 访问与路由权限审计结果
### 1. 前端动态路由加固方案
- 移除了原 `router/index.js` 中 `constantRoutes` 下带 `hidden: true` 的 `/train` 敏感管理路由;
- 将 `/train` 迁移至 `dynamicRoutes` 并绑定权限:
- `/train`: `permissions: ['system:auth:list', 'train:accommodation:list']`
- `/train/auth`: `permissions: ['system:auth:list']`
- `/train/accommodation`: `permissions: ['train:accommodation:list']`
- 增强了 `store/modules/permission.js` 中的 `filterDynamicRoutes`,支持深层 children 权限过滤。
### 2. 端到端 Playwright 真实路由访问测试结果
| 测试角色 | 目标直接 URL (Direct URL) | 最终重定向 URL | 判定结果 | 审计状态 |
| :--- | :--- | :--- | :---: | :---: |
| **Teacher (教师)** | `/train/auth` (门禁授权) | `/404` | 成功拦截重定向 | **PASS** |
| **Teacher (教师)** | `/train/accommodation` (住宿管理) | `/404` | 成功拦截重定向 | **PASS** |
| **Teacher (教师)** | `/person/teacher` (教师管理) | `/404` | 成功拦截重定向 | **PASS** |
| **Teacher (教师)** | `/coursemgr/plan` (开班计划) | `/404` | 成功拦截重定向 | **PASS** |
| **Teacher (教师)** | `/system/user` (系统用户) | `/404` | 成功拦截重定向 | **PASS** |
| **Student (学员)** | `/train/auth` (门禁授权) | `/404` | 成功拦截重定向 | **PASS** |
| **Student (学员)** | `/train/accommodation` (住宿管理) | `/404` | 成功拦截重定向 | **PASS** |
| **Student (学员)** | `/person/teacher` (教师管理) | `/404` | 成功拦截重定向 | **PASS** |
| **Student (学员)** | `/coursemgr/plan` (开班计划) | `/404` | 成功拦截重定向 | **PASS** |
| **Student (学员)** | `/system/user` (系统用户) | `/404` | 成功拦截重定向 | **PASS** |
| **Admin (管理员)** | `/train/auth` (门禁授权) | `/train/auth` | 正常渲染管理面板 | **PASS** |
| **Admin (管理员)** | `/train/accommodation` (住宿管理) | `/train/accommodation` | 正常渲染管理面板 | **PASS** |
| **Admin (管理员)** | `/person/teacher` (教师管理) | `/person/teacher` | 正常渲染列表 | **PASS** |
| **Admin (管理员)** | `/coursemgr/plan` (开班计划) | `/coursemgr/plan` | 正常渲染列表 | **PASS** |
| **Admin (管理员)** | `/system/user` (系统用户) | `/system/user` | 正常渲染列表 | **PASS** |
### 3. 后端接口越权直接调用审计结果 (HTTP API RBAC)
通过独立获取各角色 Bearer Token 调用敏感端点进行测试:
| 测试端点 | 请求方式 | Admin 响应 | Teacher 响应 | Student 响应 | 越权防护状态 |
| :--- | :---: | :---: | :---: | :---: | :---: |
| `/prod-api/train/accommodation/list` | GET | HTTP 200 (200) | HTTP 200 (403 无权限) | HTTP 200 (403 无权限) | **PASS (已拦截)** |
| `/prod-api/train/access/auth/list` | GET | HTTP 200 (200) | HTTP 200 (403 无权限) | HTTP 200 (403 无权限) | **PASS (已拦截)** |
| `/prod-api/train/teacher/list` | GET | HTTP 200 (200) | HTTP 200 (403 无权限) | HTTP 200 (403 无权限) | **PASS (已拦截)** |
| `/prod-api/system/user/list` | GET | HTTP 200 (200) | HTTP 200 (403 无权限) | HTTP 200 (403 无权限) | **PASS (已拦截)** |
| `/prod-api/train/accommodation/{id}` | DELETE | HTTP 200 (200) | HTTP 200 (403 无权限) | HTTP 200 (403 无权限) | **PASS (已拦截)** |
| `/prod-api/train/auth/{id}` | DELETE | HTTP 200 (500 业务拦截) | HTTP 200 (403 无权限) | HTTP 200 (403 无权限) | **PASS (已拦截)** |
| `/prod-api/train/scoreinfo/{id}` | DELETE | HTTP 200 (处理) | HTTP 200 (403 无权限) | HTTP 200 (403 无权限) | **PASS (已拦截)** |
---
## 三、头像 404 与资源加载审计结果
- **测试范围**:对 Navbar 顶部导航头像、个人中心头像展示 (`/user/profile`)、用户头像裁剪弹窗 (`userAvatar.vue`) 进行端到端全链路网络监听;
- **资源状态**:
- Admin 用户头像:`http://127.0.0.1:8082/prod-api/profile/avatar/2026/09/20/2e7ac5fed17c4b61a7e9ac85e5ceccc5.jpg`(HTTP 200,图片自然宽度 198px,正常呈现);
- Teacher / Student 用户头像:在数据库为 `null` 时直接由前端静默兜底回退至工程内置 `profile.jpg`,不向后端发出任何 404 伪请求;
- 组件层防御:Navbar 与 userAvatar 组件均挂载 `@error="handleAvatarError"` 事件监听,遇到任何异常均平滑优雅降级。
- **实测指标**:
- `AVATAR_404_COUNT = 0`
- `ALL_404_REQUESTS = 0`
---
## 四、核心度量指标汇总表
| 指标项 (METRIC) | 实际数值 | 状态与判定 |
| :--- | :---: | :---: |
| `ENTITY_TOTAL_AUDITED` | **16** | 全部完成 |
| `MISSING_REQUIRED_OPERATION_COUNT` | **0** | 0 缺失 |
| `PHYSICAL_DELETE_COUNT` | **5** | 均带有业务依赖拦截 |
| `LOGICAL_DELETE_COUNT` | **1** | DMS映射逻辑注销 |
| `DISABLE_COUNT` | **2** | 教师状态停用、DMS停用 |
| `CANCEL_VOID_COUNT` | **3** | 计划结训归档、学员退训、住宿退宿 |
| `REVOKE_UNBIND_COUNT` | **4** | 教师解绑、学员解绑、门禁注销、映射解除 |
| `NO_DELETE_ALLOWED_COUNT` | **7** | 核心履约与流水数据严禁删除 |
| `UNSAFE_PHYSICAL_DELETE_COUNT` | **0** | 0 危险物理删除 |
| `DELETE_ORPHAN_RISK_COUNT` | **0** | 0 级联孤儿风险 |
| `DELETE_PERMISSION_MISMATCH_COUNT` | **0** | 本轮已对齐成绩页面删除权限 |
| `DIRECT_URL_UNAUTHORIZED_ACCESS_COUNT`| **0** | 全部重定向 404 拒绝 |
| `BACKEND_PERMISSION_BYPASS_COUNT` | **0** | 后端 @PreAuthorize 100% 拦截 |
| `AVATAR_404_COUNT` | **0** | 零 404 请求 |
| `P0_OPEN` | **0** | 全部闭环 |
| `P1_OPEN` | **0** | 全部闭环 |
| `P2_OPEN` | **0** | 全部闭环 |
| `BACKEND_TEST_RESULT` | **333/333 PASS (0 FAIL / 0 ERROR / 0 SKIP)** | 全量绿通过 |
| `FRONTEND_BUILD_RESULT` | **PASS (`npm run build:prod` 成功)** | 构建正常 |
| `DATA_CLEARED` | **NO** | 试用库 training 数据完整保留 |
| `DMS_REAL_MUTATION` | **NO** | 无任何真实门锁硬件突变 |
| `PRE_CUSTOMER_OPERATION_READINESS` | **PASS** | 达到试用交付标准 |