zlwu 1 miesiąc temu
rodzic
commit
103a53f6f3

+ 16 - 3
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);

+ 21 - 1
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值
 
         // 特定缓存配置

+ 21 - 2
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 {
 
     /**
      * 新增学生学籍异动
+     * <p>权限与前端按钮对齐:退学/休学/复学/毕业 任一按钮权限,或历史 add 权限均可</p>
      */
-    @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 {
 
     /**
      * 批量新增学生学籍异动(用于前端提交多个学生的异动)
+     * <p>权限与前端按钮对齐:退学/休学/复学/毕业 任一按钮权限,或历史 add 权限均可</p>
      */
-    @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")