Files
railwaysystem/docs/development/rbac-r2/00_current_state.md
T

197 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RailwaySystem 一期 RBAC 当前状态审计报告 (Current State Audit)
- **工程版本**: RailwaySystem 一期正式发布版本 (Commit: `e4a07a2f47d1e78a1d3cefda0228b1c65e00bad8`)
- **审计日期**: 2026-09-15
- **审计目标**: 彻底排查系统当前在认证主体、注册策略、角色模型、权限字符串、实体绑定、数据范围与 IDOR 风险方面的技术现状,为 R.2 三角色(Admin, Teacher, Student)RBAC 闭环提供客观准确的事实依据。
---
## 1. 认证主体与安全框架机制现状
### 1.1 认证基础设施 (Spring Security + JWT)
- **核心依赖**: Spring Boot Security + JJWT + Redis。
- **登录端点**: `POST /login` (`com.ruoyi.web.controller.system.SysLoginController`)。
- **认证载体**: 认证成功后,系统在内存及 Redis 中维护 `com.ruoyi.common.core.domain.model.LoginUser`:
- `user`: 关联底层系统用户实体 `com.ruoyi.common.core.domain.entity.SysUser` (表 `sys_user`)。
- `roles`: 用户持有的角色标识集合 `Set<String>` (对应 `sys_role.role_key`)。
- `permissions`: 用户拥有的功能权限集合 `Set<String>` (对应 `sys_menu.perms`)。
- `token`: 客户端请求头中携带的 `Bearer <JWT_TOKEN>`,由 `JwtAuthenticationTokenFilter` 拦截解析并放入 `SecurityContextHolder`。
### 1.2 动态菜单与前端路由生成逻辑
1. 客户端登录成功后发起 `GET /getRouters` 请求,进入 `SysMenuController.getRouters()`。
2. 调用 `SysMenuServiceImpl.selectMenuTreeByUserId(userId)`:
- 若 `userId = 1L` (即超级管理员),直接无条件查询整张 `sys_menu` 表中所有状态为正常 (`status='0'`) 且为目录/菜单类型 (`menu_type IN ('M', 'C')`) 的记录,组装为树形路由返回。
- 若为普通用户,通过关联表 `sys_user_role -> sys_role_menu -> sys_menu` 进行筛选,仅返回该用户角色分配的可见菜单。
3. 前端在 Vuex `permission` store (`store/modules/permission.js`) 中通过 `filterAsyncRouter` 动态生成 Vue-Router 路由表并挂载至侧边栏。
### 1.3 按钮权限生效与 Controller 权限校验
- **后端方法级安全**:
- 全局配置开启 `@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)`。
- Controller 方法使用 `@PreAuthorize("@ss.hasPermi('train:teacher:list')")` 或 `@ss.hasAnyPermi('...')` 注解。
- 由 Spring EL 表达式调用容器内注册名为 `ss` 的组件 `com.ruoyi.framework.web.service.PermissionService`。
- **前端按钮级控制**:
- 登录后调用 `GET /getInfo` 获取 `roles` 与 `permissions` 集合,存储于 `store.getters.permissions`。
- 自定义指令 `v-hasPermi="['train:teacher:add']"` 在 DOM 渲染时比对权限集合,若不满足则通过 `el.parentNode.removeChild(el)` 物理移除 DOM 节点。
### 1.4 超级管理员通配逻辑
- **实现位置**: `SysPermissionService.java` 与 `PermissionService.java`。
- **判断逻辑**:
- 在 `SysPermissionService.getRolePermission()` 中:`if (user.isAdmin()) { roles.add("admin"); }`。
- 在 `SysPermissionService.getMenuPermission()` 中:`if (user.isAdmin()) { perms.add("*:*:*"); }`。
- 在 `PermissionService.hasPermissions()` 中:`permissions.contains("*:*:*") || permissions.contains(permission)`。
- **审计结论**: 当前生产系统 100% 依赖超级管理员 `*:*:*` 通配权限在运行,系统从未对普通角色或受限用户进行过真实路由加载与接口鉴权。
---
## 2. 用户注册功能现状审计
### 2.1 注册页面与入口代码现状
- **前端存在注册页面**: `build_ui/src/views/register.vue` (完整包含用户名、密码、确认密码、验证码表单)。
- **登录页入口暴露**: `build_ui/src/views/login.vue` 中第 61 行存在写死的局部变量:
```javascript
// 注册开关
register: true,
```
并在模板中渲染了超链接:
```html
<div v-if="register" style="float: right;">
<router-link class="link-type" :to="'/register'">立即注册</router-link>
</div>
```
### 2.2 为什么点击注册出现“当前系统没有开启注册功能!”
- **后端拦截真相**:
- 后端注册接口 `POST /register` 存在于 `SysRegisterController.java`。
- 控制器入口第一行执行配置项检查:
```java
if (!("true".equals(configService.selectConfigByKey("sys.account.registerUser"))))
{
return error("当前系统没有开启注册功能!");
}
```
- 数据库 `sys_config` 表中配置键 `sys.account.registerUser` 的当前值为 `false` (或未设为 `true`)。
- **审计结论**:
1. 并非后端接口缺失或路由崩溃,而是标准的受控业务配置拦截。
2. 在铁路职工技能培训和专业认证系统中,账号具有强实名与组织隶属要求,**严禁开放互联网自主注册**。
3. 当前前端显示“立即注册”而点击后报错,给用户造成“系统功能损坏”的误解。
4. **正式一期建议**: 将 `login.vue` 中的 `register` 设置为 `false`(彻底隐藏注册超链接),并在登录卡片底部增加提示:“*账号由培训管理部门统一创建与维护*”。保持后端受控关闭状态不变。
---
## 3. Teacher 实体与账号绑定现状审计
### 3.1 实体与数据表结构检查
- **数据库表 `train_teacher`**:
- 在数据库 V2 迁移脚本 `003_alter_master_tables.sql` 中,已执行:
```sql
ALTER TABLE train_teacher ADD COLUMN user_id BIGINT;
COMMENT ON COLUMN train_teacher.user_id IS 'sys_user登录账号逻辑关联ID';
```
- 物理表结构中已存在 `user_id` 列,但**无外键约束,无唯一索引,允许为 NULL**。
- **Java 领域实体 `TrainTeacher.java`**:
- 存在属性 `private Long userId;` 及其 getter/setter。
- **MyBatis 映射 `TrainTeacherMapper.xml`**:
- `<result property="userId" column="user_id" />` 已配置。
- `insertTrainTeacher` 与 `updateTrainTeacher` 中包含 `<if test="userId != null">user_id,</if>`。
### 3.2 业务逻辑与控制器检查
- **`TrainTeacherController.java` 与 `TrainTeacherServiceImpl.java`**:
- 全类无任何针对 `userId` 的校验、赋值、账号生成或绑定逻辑。
- 管理员通过后台添加或修改教师时,表单不接收 `userId`,也不生成 `sys_user` 记录。
- **前端 `views/train/teacher/index.vue`**:
- 教师录入表单仅包含:教师姓名、性别、出生日期、联系电话、邮箱、所属单位、职务职称、备注。
- 完全无“关联登录账号”、“用户名”或“初始密码”字段。
- **审计结论**:
- `TEACHER_ACCOUNT_BINDING_CURRENT = NONE`
- 虽有列名预留,但业务链路、控制层与展示层中账号绑定机制完全为 0。教师人员根本无法登录系统。
---
## 4. Student 实体与账号绑定现状审计
### 4.1 实体与数据表结构检查
- **数据库表 `train_student`**:
- 在 V2 迁移脚本 `003_alter_master_tables.sql` 中已执行:
```sql
ALTER TABLE train_student ADD COLUMN user_id BIGINT;
COMMENT ON COLUMN train_student.user_id IS 'sys_user账号逻辑关联';
```
- 物理表结构中已存在 `user_id` 列,但同样**无外键、无唯一约束,全部为 NULL**。
- **Java 领域实体 `TrainStudent.java`**:
- 存在 `private Long userId;` 及其 getter/setter。
- **MyBatis 映射 `TrainStudentMapper.xml`**:
- 映射标签与 insert/update 动态 SQL 中存在 `user_id`。
### 4.2 业务逻辑与控制器检查
- **`TrainStudentController.java` 与 `TrainStudentServiceImpl.java`**:
- 仅处理学员档案信息录入与身份证加密存储(密文、Hash、后4位),完全没有与 `sys_user` 的联动。
- **前端 `views/train/student/index.vue`**:
- 仅维护学员姓名、性别、身份证号、手机、邮箱、单位、来源类型等,无登录账号绑定功能。
- **审计结论**:
- `STUDENT_ACCOUNT_BINDING_CURRENT = NONE`
- 学员主体与系统用户完全解耦,学员无法登录系统,无法查看个人培训档案与课表。
---
## 5. 系统角色模型 (SysRole) 现状审计
### 5.1 数据库已有预置角色
通过检索 `sys_role` 初始化数据,系统当前仅定义了以下 2 个角色:
1. `role_id = 1`: 角色名称: `超级管理员`,角色字符: `admin`,数据范围: `1 (全部数据权限)`。
2. `role_id = 2`: 角色名称: `普通角色`,角色字符: `common`,数据范围: `2 (自定义数据权限)`。
### 5.2 培训专属角色现状
- 经全量检查,系统中**不存在**以下任何角色定义:
- `train_admin` (培训管理员)
- `train_teacher` (培训教师)
- `train_student` (参训学员)
- 审计结论:
- `TRAIN_ADMIN_ROLE_CURRENT = NONE`
- `TRAIN_TEACHER_ROLE_CURRENT = NONE`
- `TRAIN_STUDENT_ROLE_CURRENT = NONE`
---
## 6. 权限字符串与前后端契约审计
### 6.1 前后端与数据库权限命名现状
全量扫描发现以下三大典型不一致与缺失:
| 模块类别 | 典型权限前缀 | 前端现状 | 后端现状 | 数据库 `sys_menu.perms` 现状 | 判定结论 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| 教师管理 | `train:teacher:*` | `train:teacher:add/edit` | `train:teacher:list/add/edit/query` | **缺失对应按钮权限行** | 前后端命名一致,但 DB 权限行缺失 |
| 学员管理 | `train:student:*` | `train:student:add/query` | `train:student:list/add/query` | **缺失对应按钮权限行** | 前后端一致,DB 缺失 |
| 电子班牌 | `system:board:*` vs `train:board:*` | `system:board:add/edit/remove/export` | `system:board:list/add/query/edit/remove/export` | **缺失** | 命名混用 `system:`,未归入 `train:` |
| 遗留模块 | `train:auth:*` / `train:role:*` | `train:auth:add...`, `train:role:add...` | **无对应 Controller** | **无** | 废弃遗留前端页面引入的野权限 |
| 考勤报到 | `train:report:*`, `train:attendance:*` | 已正式统一 | 已正式统一 | 最近 Migration 已对齐 | **MATCH** (标准契约) |
---
## 7. 数据范围隔离与 IDOR 风险审计
### 7.1 数据范围现状
- `TEACHER_DATA_SCOPE_CURRENT = NONE (无任何基于当前教师身份的隔离逻辑)`
- `STUDENT_DATA_SCOPE_CURRENT = NONE (无任何基于当前学员身份的隔离逻辑)`
### 7.2 IDOR (越权直接对象引用) 漏洞排查
当前所有 Controller 端点直接接收前端提交的主键参数,且直接调用底层无范围过滤的查询/更新方法:
- **学员档案越权**: `GET /train/student/{sId}`,只要具备查询权限,输入任意 `sId` 即可获取他人隐私档案。
- **课表越权**: `GET /train/plans/{planId}/timetable`,无计划归属校验。
- **考勤越权**: `GET /train/checkin/list`,传入任意 `studentId` 可获取全量打卡记录。
- **学员画像越权**: `GET /train/portrait/{portraitId}`,无身份校验直接输出他人综合评定。
- **审计结论**: `IDOR_RISK_COUNT = 8 (高危端点)`,若直接开放多角色登录而未重构数据范围,系统将面临严重横向越权风险。
---
## 8. 当前状态审计总结表
| 审计维度 | 当前客观状态 | 严重度 / 影响 |
| :--- | :--- | :--- |
| **当前认证主体** | `sys_user` (LoginUser),通过 JWT 认证,超管使用 `*:*:*` 通配放行 | 正常,但无非超管业务应用 |
| **教师账号绑定** | `NONE` (物理列存在,业务全链路 0 绑定) | **阻断** Teacher 无法登录 |
| **学员账号绑定** | `NONE` (物理列存在,业务全链路 0 绑定) | **阻断** Student 无法登录 |
| **自主注册现状** | 前端界面暴露链接,后端配置受控关闭 (400 提示) | 体验缺陷,应安全隐藏入口 |
| **业务专属角色** | `train_admin`, `train_teacher`, `train_student` 全部缺失 | **缺失** 基础角色模型 |
| **数据范围隔离** | 教师与学员数据范围隔离为 `NONE`,IDOR 风险端点达 8 处 | **高危** 缺乏横向隔离 |
| **权限命名空间** | 存在 `system:board:*` 混杂及 DB 菜单按钮 perms 缺失 | 规范缺陷,需发布补丁脚本 |