# 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` (对应 `sys_role.role_key`)。 - `permissions`: 用户拥有的功能权限集合 `Set` (对应 `sys_menu.perms`)。 - `token`: 客户端请求头中携带的 `Bearer `,由 `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
立即注册
``` ### 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`**: - `` 已配置。 - `insertTrainTeacher` 与 `updateTrainTeacher` 中包含 `user_id,`。 ### 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 缺失 | 规范缺陷,需发布补丁脚本 |