# 代码审查报告:性能问题与安全隐患 **项目**:bschclassmanage(Spring Boot 2.7.18 + Spring Data JPA + Redis + fastjson 1.2.80 + CAS/云海平台认证) **审查范围**:`Repository`、`service/impl/**`、`controller/**`、`utils/**`、`config/**` **结论**:发现 **1 个严重、4 个高危、3 个中危、若干低危** 问题。主要集中在 **鉴权越权(IDOR)、敏感信息泄露、Redis 缓存管理接口、TLS 校验关闭、fastjson 反序列化、部分查询缺分页**。 --- ## 🔴 严重 (Critical) ### 1. CacheController 提供清空/枚举/删除整个 Redis 的接口,且无角色校验 + 通配 CORS - **位置**:`controller/api/CacheController.java:39-166` - **接口**:`GET /api/cache/keys`(默认 `pattern=*` 枚举所有 key)、`POST /api/cache/delete`、`POST /api/cache/deletePattern`、`POST /api/cache/clearAll`(`flushDb()`) - **风险**:类上 `@CrossOrigin(originPatterns={"*"}, allowCredentials="true")`,且所有方法 **无任何角色/超管校验**。任何已登录用户(含任意学校教师/管理员)都能查看、删除 key,甚至 `flushDb()` 清空整个 Redis 实例,影响 **所有租户** 的缓存与会话;通配 CORS 还可被第三方网页滥用。 - **修复**:删除 `clearAll`/`deletePattern` 或加超管 `@PreAuthorize`;`keys` 改为 `SCAN`;移除通配 CORS;运维类接口限制内网/独立端口。 --- ## 🟠 高危 (High) ### 2. SchCustomGroupService 跨租户越权(IDOR) - **位置**:`service/impl/SchCustomGroupService.java:375 / 440 / 515`(`saveMembers` / `setLeaders` / `deleteSchCustomGroup`) - **问题**:`teacherCustomGroupRepository.findById(groupId)` 加载组时 **未校验该组属于当前 `user.getSchId()`**,而 `groupId` 直接来自请求 DTO。 - **风险**:A 校用户可传 B 校的 `groupId`,对 B 校自定义组执行改/删(越权写)。 - **修复**:改为 `findByIdAndSchId(groupId, user.getSchId())`,或加载后断言 `existing.getSchId().equals(schId)` 再操作。 ### 3. 字典/课程/高职班级更新接口跨租户越权(6 处) - **位置**: - `SchDicMajorsService.java:104` - `SchDicDivisionService.java:99` - `SchDicCampusService.java:104` - `SchDicSubjectService.java:187` - `SchDicCourseService.java:157` - `classes/SchClassHigherService.java:114` - **问题**:更新分支均 `findById(id)` 后用 `BeanUtils.copyProperties` 覆盖字段,但 **未校验 `existing.getSchId()==schId`**。同名校验仅限本租户,无法阻止越权改写他校记录的字段(名称/编码等)。 - **风险**:A 校用户可持 B 校记录 ID 篡改 B 校字典数据。 - **修复**:用 `findByIdAndSchId` 或在 copy 前加 `existing.getSchId().equals(schId)` 断言。(注意:对应的 `delete` 方法已用 `findByIdAndSchId`,本身是安全的,建议统一。) ### 4. HttpUtils 全局禁用 TLS 证书/主机名校验(中间人风险) - **位置**:`utils/HttpUtils.java:19-54`(static 块 `trustAllHttpsCertificates()` + `setDefaultHostnameVerifier((h,s)->true)`) - **问题**:在类加载时 **对 JVM 内所有 `HttpsURLConnection` 关闭证书链与主机名校验**;`get()` 仅处理 301、无递归深度限制、`contentLen<=0` 时把响应无限读入内存。 - **风险**:所有出栈 HTTPS 可被中间人劫持/篡改;重定向缺陷可栈溢出或内存耗尽(DoS)。 - **修复**:仅对特定调用使用显式 `SSLContext`/`TrustManager`(至少校验 CA);重定向用 `HttpURLConnection` 原生 follow;限制最大响应体积。 ### 5. 敏感信息暴露:身份证号明文返回 - **位置**: - `SchTeacherService.java:103`(`BeanUtils.copyProperties` 把 `SchTeacher.idCard` 拷入 `SchTeacherVO`,经 `SchTeacherController:44` 返回) - `student/SchStudentQueryService.java:326`(拷入 `SchStudentVO`) - `TeacherSchVO` / `SchTeacherTOutVO.idCard` 字段 - **风险**:教师/学生身份证号明文返回前端,违反个人信息保护要求,易泄露。 - **修复**:VO 去除 `idCard`,或加 `@JsonIgnore`,必要时脱敏(如 `110***********1234`)。 --- ## 🟡 中危 (Medium) ### 6. fastjson 反序列化 autoType 风险 + 白名单与包名不匹配 - **位置**:`config/RedisConfig.java:58-60`(`FastJsonRedisSerializer` 写入 `@type` + `ParserConfig.addAccept("com.bestsch")`);`utils/RedisUtil.java:889 / 898 / 921`(`JSON.parseObject/parseArray(JSON.toJSONString(obj), clazz)`) - **问题**:fastjson **1.2.80** 存在已知 autoType 绕过 CVE;而 `addAccept("com.bestsch")` 与本项目包名 `com.yunhai.bschclassmanage` **不匹配** —— 白名单既可能失效(领域对象反序列化失败),又未真正起到隔离作用。 - **修复**:升级 fastjson ≥ 1.2.83(或迁移 fastjson2 并开启 `safeMode`),关闭 autoType;优先改用 Jackson(`GenericJackson2JsonRedisSerializer`)并显式类型;删除 `addAccept("com.bestsch")`。 - **补充**:`OtherUtil.java:42,59` 的 `JSON.parse` / `JSONObject.parse` 仅用于内部 JSON 校验,未见到 HTTP 直接入口,风险低,但建议统一换成 Jackson。 ### 7. Redis 阻塞扫描 + 高危死代码(路径遍历) - **位置**: - `utils/RedisUtil.java:871` `keys(pattern)` 被 `CacheController.java:93,134` 调用(`KEYS` 为 O(N) 阻塞命令,大库会卡死 Redis) - `utils/DownloadFileUtil.java:30-36` `new File(filePath)` 路径遍历、`FileInputStream` 未用 try-with-resources 关闭、`System.out.println(file.exists())` 调试残留(当前无调用方,属高危死代码) - **修复**:`keys` 改 `SCAN`;`DownloadFileUtil` 限定在基准目录内、try-with-resources、删除调试输出,或直接删除该工具类。 ### 8. 部分查询缺分页 / 全量加载 - **位置**: - `classes/SchClassQueryService.java:127` `searchBaseSchClass` 返回 `findAll(spec)` 全量 - `student/SchStudentQueryService.java:202` 学生查询返回 `List` 无 `Pageable` - (教师查询已分页,良好) - **风险**:大班级/全校学生列表全量返回,内存与响应时间随数据量线性增长。 - **修复**:改为带 `Pageable` 的分页查询;前端列表必须分页。 --- ## 🟢 低危 / 建议 ### 9. 资源未关闭 - `teacher/SchTeacherImportService.java:64` `ExcelImportUtil.importExcel(file.getInputStream(), ...)` 的 `InputStream` 未用 try-with-resources(其余导入服务已用)。 - **修复**:包进 try-with-resources。 ### 10. 冗余写 - `classes/SchClassImportService.java:195/201` 先 `saveAll`,再循环补 `classId` 后二次 `saveAll`。 - **修复**:先算好 `classId` 再一次性 `saveAll`。 ### 11. 缓存击穿防护不完整 - `service/com/ComSchTreeService.getGroupTree` / `getSchoolList` 缓存回填未加锁(`SchClassCacheService` 已用 `synchronized` 双检,正确)。 - **修复**:对无锁缓存方法加本地/分布式锁,避免并发缓存踩踏。 ### 12. 纵深防御 - `SysOperationLogServiceImpl.java:73` `getById(id)` 不带 `schId` 过滤(当前无 Controller 暴露,建议补)。 - `SchStudent/SchTeacher` 等 `save` 已做 `schId` 校验(良好)。建议统一对所有 `findById` 入口补 `schId` 过滤。 --- ## ✅ 已做好的地方(正面确认) - **Repository 的 `@Query` 全部使用命名参数(`:param`)**,无字符串拼接,SQL 注入风险低。 - **批量更新/升级**(如 `SchClassUpgradeService.batchUpgradeGrade`/`batchGraduate`、学生批量转班)均用单条 `@Modifying` 批量 SQL,**无 N+1**。 - **四个导入服务均使用 `saveAll` 批量**,无循环单条 `save`。 - **删除类操作**(`SchClassInfoService`、`SchTeacherInfoService:168`、`SchStudentInfoService:149`、`SchClassTeachInfoService:112`)已做 `schId` 校验。 --- ## 🚑 优先级修复建议 1. **立即**:`CacheController` 关闭/加超管校验 + 去掉通配 CORS(影响全部租户) 2. **立即**:`SchCustomGroupService` + 6 个字典更新接口补 `schId` 校验(IDOR) 3. **立即**:身份证号 VO 脱敏/移除 4. **尽快**:`HttpUtils` 修复 TLS 校验、重定向、响应体积限制 5. **尽快**:fastjson 升级/关 autoType,Redis 序列化改 Jackson 6. **计划**:查询分页、资源关闭、缓存锁、纵深防御