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

12 KiB
Raw Blame History

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 行存在写死的局部变量:
    // 注册开关
    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)。
  • 审计结论:
    1. 并非后端接口缺失或路由崩溃,而是标准的受控业务配置拦截。
    2. 在铁路职工技能培训和专业认证系统中,账号具有强实名与组织隶属要求,严禁开放互联网自主注册。
    3. 当前前端显示“立即注册”而点击后报错,给用户造成“系统功能损坏”的误解。
    4. 正式一期建议: 将 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。
  • 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。
  • 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 缺失 规范缺陷,需发布补丁脚本