7.8 KiB
7.8 KiB
RailwaySystem 数据范围与 IDOR 安全防护设计 (Data Scope & IDOR Prevention)
- 设计版本: R.2A Design Freeze
- 设计日期: 2026-09-15
- 核心目标: 建立从安全凭证(
SecurityContext)到业务主体 ID 的单向信任推导链条,杜绝前端参数篡改越权,消除全站直接对象引用(IDOR)安全漏洞。
1. 数据范围核心设计原则 (Zero-Trust Frontend)
在多角色(Admin, Teacher, Student)登录体系中,数据范围安全必须建立在零信任前端参数的基石之上:
flowchart TD
Req["客户端 HTTP Request<br/>(仅携带 Bearer JWT Token,禁止/忽略 query 或 body 中的 identityId)"]
Filter["Spring Security Filter: JwtAuthenticationTokenFilter<br/>(验签并提取 claims 中的 userId)"]
SecCtx["SecurityContextHolder.getContext().getAuthentication()<br/>(获取受信任的 LoginUser.getUserId())"]
Helper["Service 内部数据推导 (SecurityContextHelper)"]
Req --> Filter
Filter --> SecCtx
SecCtx --> Helper
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 核心铁律
- 禁止直接接收外部身份 ID: 在教师和学员的私有业务接口中,接口方法参数严禁声明
@RequestParam String studentId或@PathVariable String teacherId。 - 强制服务端单向解析: 所有业务查询条件中的
studentId、teacherId必须统一在 Service 事务内由SecurityUtils.getUserId()动态查询或注入。 - 严格归属校验 (Ownership Assertion): 在涉及具体资源主键操作(如查看某个具体
reportId、checkinId、enrollmentId)时,SQL 中必须强制拼接AND student_id = #{currentStudentId}或AND teacher_id = #{currentTeacherId},不满足条件时直接返回404 Not Found或403 Forbidden,坚决不泄露数据存在性。
2. 教师 (Teacher) 业务数据范围推导链条
教师登录后,其可访问的所有教学资源必须严格基于其受聘授课关系动态推导:
flowchart TD
User["sys_user (userId)"] -->|"WHERE user_id = #{userId}"| Teacher["train_teacher (t_id)"]
Teacher -->|"排课直接授课"| Lesson["timetable_lesson<br/>WHERE teacher_id = #{t_id}"]
Teacher -->|"计划课程关联"| PlanCourse["train_plan_course<br/>WHERE teacher_id = #{t_id}"]
Teacher -->|"关联教学班级"| ClassPlan["train_class_plan<br/>WHERE plan_id IN (授课班级集合)"]
Lesson --> Checkin["student_checkin<br/>(本人课次考勤流水)"]
ClassPlan --> Enrollment["training_enrollment<br/>(我带的班级学员名单)"]
- 授课课表数据: 仅能查到
train_timetable_lesson.teacher_id = #{currentTeacherId}的课次。 - 关联班级数据: 仅能查到由当前教师担任授课任务的
plan_id集合。 - 班级学员名单: 仅能读取自己任教班级名单,严禁读取全校其他培训班的学员档案,且读取名单时必须对身份证号、住址等个人隐私字段进行脱敏(仅显示后四位)。
3. 学员 (Student) 私有数据范围推导链条
学员登录后,进入极简个人培训中心,数据范围完全收敛于“本人”:
flowchart TD
User["sys_user (userId)"] -->|"WHERE user_id = #{userId}"| Student["train_student (s_id)"]
Student --> Profile["个人档案 (只读)"]
Student --> Enrollment["参训班级名单 (Enrollment)<br/>WHERE s_id = ..."]
Student --> Report["个人报到 (Report)<br/>WHERE s_id = ..."]
Student --> Checkin["个人考勤记录 (Checkin)<br/>WHERE s_id = ..."]
Student --> Portrait["个人画像 / 学时累计<br/>WHERE s_id = ..."]
Enrollment --> Timetable["班级参训课表<br/>(仅限正常在训)"]
- 学员档案: 仅能读取与更新本人通讯电话、邮箱。
- 学员课表: 仅能读取本人已分班入班(且状态为待报到、已报到或已完成)的班级课次,严禁获取其他班级的教学日程。
- 考勤与报到: 报到记录、打卡记录、学时累计与学员画像全部以
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。 - 防护治理策略:
- 管理接口封闭: 原有
/train/student/**、/train/teacher/**、/train/enrollments/**仅保留给train_admin。 - 自服务通道独立: 为 Teacher 与 Student 开辟全新的
/train/me/**API,彻底切断直接对象引用漏洞源。
- 管理接口封闭: 原有