From 103a53f6f39b840086a923dbe47f14f387fb2b70 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Mon, 28 Sep 2026 10:09:09 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/redis/config/RedisConfig.java | 19 ++++++++++++--- .../org/dromara/xg/config/XgCacheConfig.java | 22 +++++++++++++++++- .../XgStudentStatusChangeController.java | 23 +++++++++++++++++-- 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/backend/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/RedisConfig.java b/backend/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/RedisConfig.java index 7ba94751..af9683c4 100644 --- a/backend/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/RedisConfig.java +++ b/backend/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/RedisConfig.java @@ -2,9 +2,11 @@ package org.dromara.common.redis.config; import cn.hutool.core.util.ObjectUtil; import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; +import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator; +import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; @@ -51,8 +53,19 @@ public class RedisConfig { om.registerModule(javaTimeModule); om.setTimeZone(TimeZone.getDefault()); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); - // 指定序列化输入的类型,类必须是非final修饰的。序列化时将对象全类名一起保存下来 - om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL); + // 使用包名白名单替代 LaissezFaireSubTypeValidator,降低 Redis 被写入恶意 @class 时的反序列化 RCE 风险 + // 说明:RedisUtils/Sa-Token 以 Object 读写缓存,仍需保留类型信息;禁止完全放开任意类反序列化 + PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder() + .allowIfSubType("org.dromara.") + .allowIfSubType("cn.dev33.satoken.") + .allowIfSubType("cn.hutool.") + .allowIfSubType("java.util.") + .allowIfSubType("java.lang.") + .allowIfSubType("java.time.") + .allowIfSubType("java.math.") + .allowIfSubType("java.util.concurrent.") + .build(); + om.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); // LoggerFactory.useSlf4jLogging(true); // FuryCodec furyCodec = new FuryCodec(); // CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, furyCodec, furyCodec); diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/config/XgCacheConfig.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/config/XgCacheConfig.java index 32a61c20..223daab3 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/config/XgCacheConfig.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/config/XgCacheConfig.java @@ -1,5 +1,10 @@ package org.dromara.xg.config; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator; +import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; @@ -14,6 +19,7 @@ import org.springframework.data.redis.serializer.StringRedisSerializer; import java.time.Duration; import java.util.HashMap; import java.util.Map; +import java.util.TimeZone; /** * 学工模块缓存配置类 @@ -31,11 +37,25 @@ public class XgCacheConfig { */ @Bean("xgCacheManager") public CacheManager xgCacheManager(RedisConnectionFactory factory) { + ObjectMapper om = new ObjectMapper(); + om.registerModule(new JavaTimeModule()); + om.setTimeZone(TimeZone.getDefault()); + // 白名单限制可反序列化类型,避免 GenericJackson2JsonRedisSerializer 默认 LaissezFaire 风险 + PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder() + .allowIfSubType("org.dromara.") + .allowIfSubType("java.util.") + .allowIfSubType("java.lang.") + .allowIfSubType("java.time.") + .allowIfSubType("java.math.") + .build(); + om.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); + GenericJackson2JsonRedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer(om); + // 默认缓存配置 RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofMinutes(30)) // 默认30分钟过期 .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) - .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())) + .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer)) .disableCachingNullValues(); // 不缓存null值 // 特定缓存配置 diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgStudentStatusChangeController.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgStudentStatusChangeController.java index a3bb5de0..8e060688 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgStudentStatusChangeController.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgStudentStatusChangeController.java @@ -6,6 +6,7 @@ import lombok.RequiredArgsConstructor; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.constraints.*; import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.annotation.SaMode; import org.springframework.web.bind.annotation.*; import org.springframework.validation.annotation.Validated; import org.dromara.common.idempotent.annotation.RepeatSubmit; @@ -70,8 +71,15 @@ public class XgStudentStatusChangeController extends BaseController { /** * 新增学生学籍异动 + *

权限与前端按钮对齐:退学/休学/复学/毕业 任一按钮权限,或历史 add 权限均可

*/ - @SaCheckPermission("studentMsg:studentStatusChange:add") + @SaCheckPermission(value = { + "studentMsg:status:tuixue", + "studentMsg:status:xiuxue", + "studentMsg:status:fuxue", + "studentMsg:status:biye", + "studentMsg:studentStatusChange:add" + }, mode = SaMode.OR) @Log(title = "学生学籍异动", businessType = BusinessType.INSERT) @RepeatSubmit() @PostMapping() @@ -81,8 +89,15 @@ public class XgStudentStatusChangeController extends BaseController { /** * 批量新增学生学籍异动(用于前端提交多个学生的异动) + *

权限与前端按钮对齐:退学/休学/复学/毕业 任一按钮权限,或历史 add 权限均可

*/ - @SaCheckPermission("studentMsg:studentStatusChange:add") + @SaCheckPermission(value = { + "studentMsg:status:tuixue", + "studentMsg:status:xiuxue", + "studentMsg:status:fuxue", + "studentMsg:status:biye", + "studentMsg:studentStatusChange:add" + }, mode = SaMode.OR) @Log(title = "学生学籍异动", businessType = BusinessType.INSERT) @RepeatSubmit() @PostMapping("/batch") @@ -97,6 +112,10 @@ public class XgStudentStatusChangeController extends BaseController { /** * 复学并换班级(一次提交,事务化写入两条记录) */ + @SaCheckPermission(value = { + "studentMsg:status:fuxue", + "studentMsg:studentStatusChange:add" + }, mode = SaMode.OR) @Log(title = "学生复学并换班级", businessType = BusinessType.INSERT) @RepeatSubmit() @PostMapping("/resume-with-class-change")