Files
railwaysystem/docs/development/rbac-r2/03_data_scope_design.md
T

12 KiB

RailwaySystem 数据范围与 IDOR 安全防护设计 (Data Scope & IDOR Prevention)

  • 设计版本: R.2A Design Freeze
  • 设计日期: 2026-09-15
  • 核心目标: 建立从安全凭证(SecurityContext)到业务主体 ID 的单向信任推导链条,杜绝前端参数篡改越权,消除全站直接对象引用(IDOR)安全漏洞。

1. 数据范围核心设计原则 (Zero-Trust Frontend)

在多角色(Admin, Teacher, Student)登录体系中,数据范围安全必须建立在零信任前端参数的基石之上:

+─────────────────────────────────────────────────────────────────────────────+
|                         零信任推导核心防线 (Secure Chain)                    |
|                                                                             |
|   客户端 HTTP Request                                                       |
|       │ (仅携带 Bearer JWT Token,禁止/忽略 query 或 body 中的 identityId)     |
|       ▼                                                                     |
|   Spring Security Filter: JwtAuthenticationTokenFilter                      |
|       │ (验签并提取 claims 中的 userId)                                      |
|       ▼                                                                     |
|   SecurityContextHolder.getContext().getAuthentication()                    |
|       │ (获取受信任的 LoginUser.getUserId())                                |
|       ▼                                                                     |
|   Service 内部数据推导 (SecurityContextHelper)                              |
|       ├─ 若拥有 train_teacher 角色:                                          |
|       │    SELECT t_id FROM train_teacher WHERE user_id = #{userId}         |
|       ├─ 若拥有 train_student 角色:                                          |
|       │    SELECT s_id FROM train_student WHERE user_id = #{userId}         |
|       └─ 若拥有 admin / train_admin 角色:                                   |
|            按管理员角色规则放行或基于组织机构过滤                               |
+─────────────────────────────────────────────────────────────────────────────+

1.1 核心铁律

  1. 禁止直接接收外部身份 ID: 在教师和学员的私有业务接口中,接口方法参数严禁声明 @RequestParam String studentId 或 @PathVariable String teacherId。
  2. 强制服务端单向解析: 所有业务查询条件中的 studentId、teacherId 必须统一在 Service 事务内由 SecurityUtils.getUserId() 动态查询或注入。
  3. 严格归属校验 (Ownership Assertion): 在涉及具体资源主键操作(如查看某个具体 reportId、checkinId、enrollmentId)时,SQL 中必须强制拼接 AND student_id = #{currentStudentId} 或 AND teacher_id = #{currentTeacherId},不满足条件时直接返回 404 Not Found 或 403 Forbidden,坚决不泄露数据存在性。

2. 教师 (Teacher) 业务数据范围推导链条

教师登录后,其可访问的所有教学资源必须严格基于其受聘授课关系动态推导:

                      ┌──────────────────────┐
                      │  sys_user (userId)   │
                      └──────────┬───────────┘
                                 │ WHERE user_id = #{userId}
                                 ▼
                      ┌──────────────────────┐
                      │  train_teacher (t_id)│
                      └──────────┬───────────┘
                                 │
         ┌───────────────────────┼────────────────────────┐
         │ (排课直接授课)         │ (计划课程关联)          │ (关联教学班级)
         ▼                       ▼                        ▼
┌──────────────────┐    ┌──────────────────┐    ┌──────────────────┐
│timetable_lesson  │    │train_plan_course │    │ train_class_plan │
│WHERE teacher_id  │    │WHERE teacher_id  │    │ WHERE plan_id IN │
│ = #{t_id}        │    │ = #{t_id}        │    │ (授课班级集合)    │
└────────┬─────────┘    └──────────────────┘    └────────┬─────────┘
         │                                               │
         ▼                                               ▼
┌──────────────────┐                            ┌──────────────────┐
│student_checkin   │                            │training_enrollment
│(本人课次考勤流水)  │                            │(我带的班级学员名单)│
└──────────────────┘                            └──────────────────┘
  • 授课课表数据: 仅能查到 train_timetable_lesson.teacher_id = #{currentTeacherId} 的课次。
  • 关联班级数据: 仅能查到由当前教师担任授课任务的 plan_id 集合。
  • 班级学员名单: 仅能读取自己任教班级名单,严禁读取全校其他培训班的学员档案,且读取名单时必须对身份证号、住址等个人隐私字段进行脱敏(仅显示后四位)。

3. 学员 (Student) 私有数据范围推导链条

学员登录后,进入极简个人培训中心,数据范围完全收敛于“本人”:

                      ┌──────────────────────┐
                      │  sys_user (userId)   │
                      └──────────┬───────────┘
                                 │ WHERE user_id = #{userId}
                                 ▼
                      ┌──────────────────────┐
                      │  train_student (s_id)│
                      └──────────┬───────────┘
                                 │
   ┌─────────────┬───────────────┼───────────────┬─────────────┐
   ▼             ▼               ▼               ▼             ▼
┌───────┐ ┌──────────────┐ ┌───────────┐ ┌──────────────┐ ┌────────────┐
│ 个人  │ │ 参训班级名单 │ │ 个人报到  │ │ 个人考勤记录 │ │ 个人画像/  │
│ 档案  │ │  Enrollment  │ │  Report   │ │   Checkin    │ │  学时累计  │
│(只读) │ │WHERE s_id=...│ │WHERE s_id │ │WHERE s_id=...│ │WHERE s_id  │
└───────┘ └──────┬───────┘ └───────────┘ └──────────────┘ └────────────┘
                 │
                 ▼
          ┌──────────────┐
          │ 班级参训课表 │
          │(仅限正常在训)│
          └──────────────┘
  • 学员档案: 仅能读取与更新本人通讯电话、邮箱。
  • 学员课表: 仅能读取本人已分班入班(且状态为待报到、已报到或已完成)的班级课次,严禁获取其他班级的教学日程。
  • 考勤与报到: 报到记录、打卡记录、学时累计与学员画像全部以 student_id = #{currentStudentId} 为严格物理隔离边界。

4. 全站 IDOR 漏洞排查与加固矩阵 (IDOR_RISK_MATRIX)

本矩阵针对现有 Controller 方法进行逐一安全排查,明确其在多角色模式下的越权风险等级与防护要求:

序号 业务接口端点 当前实现机制 越权风险评定 潜在越权利用场景 R.2 加固与防护措施
01 GET /train/student/{sId} 直接根据路径参数 sId 查询并返回档案明细 AT_RISK (高危) 学员登录后篡改 sId 抓取全校任意职工身份证号与家庭隐私 管理端专有:普通学员严禁访问此端点;学员本人查阅统一走 /train/me/student
02 GET /train/teacher/{tId} 直接根据路径参数 tId 查询教师档案 AT_RISK (中危) 任意登录人员枚举读取全校师资档案 管理端专有;教师本人查阅统一走 /train/me/teacher
03 GET /train/plans/{planId}/timetable 直接根据 planId 查询整班课次 AT_RISK (高危) 未参训学员或未授课教师随意侦测其他班级排课安排 校验当前登录人是否在该计划的教师或学员名单内,否则直接拒止
04 GET /train/plans/{planId}/enrollments 直接根据 planId 查询全班学员名册 AT_RISK (高危) 普通学员篡改 planId 窃取其他班级全部学员花名册及电话 严禁学员访问;教师仅能查阅所带班级名册且敏感字段脱敏
05 GET /train/enrollments/{enrollmentId} 直接根据主键 enrollmentId 查询 AT_RISK (中危) 水平越权读取其他学员入班凭证 增加 student_id = currentStudentId 强行归属校验
06 POST /train/attendance/sign-in Body 接收 studentId, lessonId AT_RISK (高危) 学员 A 抓包伪造请求体中的 studentId,为旷课学员 B 代签 接口服务端强制断言 body.studentId == currentStudentId,否则拦截
07 PUT /train/attendance/{checkinId} 接收 checkinId 与修改状态 AT_RISK (高危) 普通学员或无关人员抓包篡改他人考勤记录 仅限拥有 train:attendance:edit 的管理员或授权教师操作
08 GET /train/checkin/{checkinId} 直接按 ID 查询单条打卡记录 AT_RISK (低危) 水平遍历探测其他人员打卡位置与时间 增加当前登录人身份归属断言
09 GET /train/portrait/{portraitId} 直接按画像 ID 返回综合能力雷达 AT_RISK (高危) 学员枚举读取其他学员测评与能力短板数据 学员查阅统一走 /train/me/portrait,底层绑定本人 ID
10 GET /train/duration/{durId} 直接按主键读取学时记录 AT_RISK (中危) 水平越权探测他人学时与上线情况 学员统一走 /train/me/duration,强制本人绑定
11 POST /train/plans (新增计划) @PreAuthorize 鉴权 SAFE 非管理员无权限直接返回 403 角色与菜单隔离即可防御
12 POST /train/plans/{id}/lessons (排课) @PreAuthorize 鉴权 SAFE 非教务人员无排课权限 角色与功能权限隔离即可防御

4.1 审计总结

  • 高危越权端点统计: IDOR_RISK_COUNT = 8。
  • 防护治理策略:
    1. 管理接口封闭: 原有 /train/student/**、/train/teacher/**、/train/enrollments/** 仅保留给 train_admin。
    2. 自服务通道独立: 为 Teacher 与 Student 开辟全新的 /train/me/** API,彻底切断直接对象引用漏洞源。