197 lines
12 KiB
Markdown
197 lines
12 KiB
Markdown
# 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 缺失 | 规范缺陷,需发布补丁脚本 |
|