# RailwaySystem 数据范围与 IDOR 安全防护设计 (Data Scope & IDOR Prevention) - **设计版本**: R.2A Design Freeze - **设计日期**: 2026-09-15 - **核心目标**: 建立从安全凭证(`SecurityContext`)到业务主体 ID 的单向信任推导链条,杜绝前端参数篡改越权,消除全站直接对象引用(IDOR)安全漏洞。 --- ## 1. 数据范围核心设计原则 (Zero-Trust Frontend) 在多角色(Admin, Teacher, Student)登录体系中,数据范围安全必须建立在**零信任前端参数**的基石之上: ```mermaid flowchart TD Req["客户端 HTTP Request
(仅携带 Bearer JWT,忽略请求体中的 identityId)"] --> Filter["Spring Security Filter (JwtAuthenticationTokenFilter)
验签并提取 claims 中的 userId"] --> SecCtx["SecurityContextHolder (受信任上下文)
获取 LoginUser.getUserId()"] --> Helper["Service 内部推导 (SecurityContextHelper)"] Helper -->|"拥有 train_teacher 角色"| TeacherRole["SELECT t_id FROM train_teacher
WHERE user_id = #{userId}"] Helper -->|"拥有 train_student 角色"| StudentRole["SELECT s_id FROM train_student
WHERE user_id = #{userId}"] Helper -->|"拥有 admin / train_admin 角色"| AdminRole["管理员放行 / 组织机构过滤"] ``` ### 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) 业务数据范围推导链条 教师登录后,其可访问的所有教学资源必须严格基于其受聘授课关系动态推导: ```mermaid flowchart TD User["sys_user (userId)"] -->|"WHERE user_id = #{userId}"| Teacher["train_teacher (t_id)"] Teacher -->|"排课直接授课"| Lesson["timetable_lesson
WHERE teacher_id = #{t_id}"] Teacher -->|"计划课程关联"| PlanCourse["train_plan_course
WHERE teacher_id = #{t_id}"] Teacher -->|"关联教学班级"| ClassPlan["train_class_plan
WHERE plan_id IN (授课班级集合)"] Lesson --> Checkin["student_checkin
(本人课次考勤流水)"] ClassPlan --> Enrollment["training_enrollment
(我带的班级学员名单)"] ``` - **授课课表数据**: 仅能查到 `train_timetable_lesson.teacher_id = #{currentTeacherId}` 的课次。 - **关联班级数据**: 仅能查到由当前教师担任授课任务的 `plan_id` 集合。 - **班级学员名单**: 仅能读取自己任教班级名单,**严禁读取全校其他培训班的学员档案**,且读取名单时必须对身份证号、住址等个人隐私字段进行脱敏(仅显示后四位)。 --- ## 3. 学员 (Student) 私有数据范围推导链条 学员登录后,进入极简个人培训中心,数据范围完全收敛于“本人”: ```mermaid flowchart TD User["sys_user (userId)"] -->|"WHERE user_id = #{userId}"| Student["train_student (s_id)"] Student --> Profile["个人档案 (只读)"] Student --> Enrollment["参训班级名单 (Enrollment)
WHERE s_id = ..."] Student --> Report["个人报到 (Report)
WHERE s_id = ..."] Student --> Checkin["个人考勤记录 (Checkin)
WHERE s_id = ..."] Student --> Portrait["个人画像 / 学时累计
WHERE s_id = ..."] Enrollment --> Timetable["班级参训课表
(仅限正常在训)"] ``` - **学员档案**: 仅能读取与更新本人通讯电话、邮箱。 - **学员课表**: 仅能读取本人已分班入班(且状态为待报到、已报到或已完成)的班级课次,严禁获取其他班级的教学日程。 - **考勤与报到**: 报到记录、打卡记录、学时累计与学员画像全部以 `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,彻底切断直接对象引用漏洞源。