12 KiB
12 KiB
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 动态菜单与前端路由生成逻辑
- 客户端登录成功后发起
GET /getRouters请求,进入SysMenuController.getRouters()。 - 调用
SysMenuServiceImpl.selectMenuTreeByUserId(userId):- 若
userId = 1L(即超级管理员),直接无条件查询整张sys_menu表中所有状态为正常 (status='0') 且为目录/菜单类型 (menu_type IN ('M', 'C')) 的记录,组装为树形路由返回。 - 若为普通用户,通过关联表
sys_user_role -> sys_role_menu -> sys_menu进行筛选,仅返回该用户角色分配的可见菜单。
- 若
- 前端在 Vuex
permissionstore (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 行存在写死的局部变量:并在模板中渲染了超链接:// 注册开关 register: true,<div v-if="register" style="float: right;"> <router-link class="link-type" :to="'/register'">立即注册</router-link> </div>
2.2 为什么点击注册出现“当前系统没有开启注册功能!”
- 后端拦截真相:
- 后端注册接口
POST /register存在于SysRegisterController.java。 - 控制器入口第一行执行配置项检查:
if (!("true".equals(configService.selectConfigByKey("sys.account.registerUser")))) { return error("当前系统没有开启注册功能!"); } - 数据库
sys_config表中配置键sys.account.registerUser的当前值为false(或未设为true)。
- 后端注册接口
- 审计结论:
- 并非后端接口缺失或路由崩溃,而是标准的受控业务配置拦截。
- 在铁路职工技能培训和专业认证系统中,账号具有强实名与组织隶属要求,严禁开放互联网自主注册。
- 当前前端显示“立即注册”而点击后报错,给用户造成“系统功能损坏”的误解。
- 正式一期建议: 将
login.vue中的register设置为false(彻底隐藏注册超链接),并在登录卡片底部增加提示:“账号由培训管理部门统一创建与维护”。保持后端受控关闭状态不变。
3. Teacher 实体与账号绑定现状审计
3.1 实体与数据表结构检查
- 数据库表
train_teacher:- 在数据库 V2 迁移脚本
003_alter_master_tables.sql中,已执行:ALTER TABLE train_teacher ADD COLUMN user_id BIGINT; COMMENT ON COLUMN train_teacher.user_id IS 'sys_user登录账号逻辑关联ID'; - 物理表结构中已存在
user_id列,但无外键约束,无唯一索引,允许为 NULL。
- 在数据库 V2 迁移脚本
- 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中已执行:ALTER TABLE train_student ADD COLUMN user_id BIGINT; COMMENT ON COLUMN train_student.user_id IS 'sys_user账号逻辑关联'; - 物理表结构中已存在
user_id列,但同样无外键、无唯一约束,全部为 NULL。
- 在 V2 迁移脚本
- Java 领域实体
TrainStudent.java:- 存在
private Long userId;及其 getter/setter。
- 存在
- MyBatis 映射
TrainStudentMapper.xml:- 映射标签与 insert/update 动态 SQL 中存在
user_id。
- 映射标签与 insert/update 动态 SQL 中存在
4.2 业务逻辑与控制器检查
TrainStudentController.java与TrainStudentServiceImpl.java:- 仅处理学员档案信息录入与身份证加密存储(密文、Hash、后4位),完全没有与
sys_user的联动。
- 仅处理学员档案信息录入与身份证加密存储(密文、Hash、后4位),完全没有与
- 前端
views/train/student/index.vue:- 仅维护学员姓名、性别、身份证号、手机、邮箱、单位、来源类型等,无登录账号绑定功能。
- 审计结论:
STUDENT_ACCOUNT_BINDING_CURRENT = NONE- 学员主体与系统用户完全解耦,学员无法登录系统,无法查看个人培训档案与课表。
5. 系统角色模型 (SysRole) 现状审计
5.1 数据库已有预置角色
通过检索 sys_role 初始化数据,系统当前仅定义了以下 2 个角色:
role_id = 1: 角色名称:超级管理员,角色字符:admin,数据范围:1 (全部数据权限)。role_id = 2: 角色名称:普通角色,角色字符:common,数据范围:2 (自定义数据权限)。
5.2 培训专属角色现状
- 经全量检查,系统中不存在以下任何角色定义:
train_admin(培训管理员)train_teacher(培训教师)train_student(参训学员)
- 审计结论:
TRAIN_ADMIN_ROLE_CURRENT = NONETRAIN_TEACHER_ROLE_CURRENT = NONETRAIN_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 缺失 |
规范缺陷,需发布补丁脚本 |