diff --git a/viewsh-module-video/viewsh-module-video-server/src/main/java/com/viewsh/module/video/framework/redis/RedisTemplateConfig.java b/viewsh-module-video/viewsh-module-video-server/src/main/java/com/viewsh/module/video/framework/redis/RedisTemplateConfig.java index 714f900f..ba4e88ff 100644 --- a/viewsh-module-video/viewsh-module-video-server/src/main/java/com/viewsh/module/video/framework/redis/RedisTemplateConfig.java +++ b/viewsh-module-video/viewsh-module-video-server/src/main/java/com/viewsh/module/video/framework/redis/RedisTemplateConfig.java @@ -1,5 +1,11 @@ package com.viewsh.module.video.framework.redis; +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.BasicPolymorphicTypeValidator; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.viewsh.module.video.gb28181.bean.MobilePosition; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -12,22 +18,65 @@ import org.springframework.data.redis.serializer.StringRedisSerializer; * Video 模块专用 RedisTemplate 配置 * 独立 Bean 名称,避免覆盖框架的 Jackson 版本 RedisTemplate * - * NOTE: Original WVP used FastJSON2 (GenericFastJsonRedisSerializer from fastjson2-extension-spring6). - * Temporarily using GenericJackson2JsonRedisSerializer as fallback until - * fastjson2-extension-spring6 is available in the local Maven repository. - * TODO: Switch back to GenericFastJsonRedisSerializer once the dependency is resolved. + *

ObjectMapper 注意点: + * 1. 注册 {@link JavaTimeModule}:支持 LocalDateTime / LocalDate 等 Java 8 时间类型 + * (否则写入含 createTime 的对象会报 "not supported by default") + * 2. 启用 default typing:存入时写入 @class 元信息,读出时才能反序列化成原类 + * (WVP 大量 Redis 用法依赖"存任意对象、读回来能强转"的能力) + * 3. typing 使用 BasicPolymorphicTypeValidator 做白名单校验,避免 gadget chain 攻击 + * + *

NOTE: Original WVP used FastJSON2. 迁移到 Jackson 后需要手动对齐 JSR310 + default typing。 */ @Configuration public class RedisTemplateConfig { + /** + * 构建一份专用于 Redis 值序列化的 ObjectMapper + * 不复用全局 Spring Boot ObjectMapper,因为 Redis 的 default typing 会改变 JSON 输出, + * 不想污染到业务接口的 JSON 响应 + */ + private static ObjectMapper buildRedisObjectMapper() { + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + // 字段可见性只放开到 field 级(配合 Lombok getter 够用),不放开 setter/creator/is-getter, + // 降低反序列化时被"非预期 setter"串通 gadget chain 的面 + mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + // default typing:写入 @class 元信息以支持多态读回。 + // + // 白名单要"够用 + 够紧": + // - 原先的 allowIfSubType(Object.class) 等价于 LaissezFaireSubTypeValidator, + // Jackson 官方明确警告会触发 CVE 级 gadget chain(jackson-databind #2367/#2996 等), + // 本模块通过 hook/SIP 信令把外部输入写进 Redis,必须收紧 + // - 按业务实际需要列举允许的 package: + // 1) com.viewsh.* —— 本模块 + 框架公共结构(CommonResult / PageResult 等) + // 2) com.alibaba.fastjson2.JSONObject/JSONArray —— WVP 历史代码存这两个类到 Redis + // 3) java.util / java.time / java.lang / java.math —— 集合/时间/数字基础类型 + // - 后续如果遇到"新 DTO 反序列化不了",优先把类放进 com.viewsh 包名下, + // 不要再退回 allowIfSubType(Object.class) + BasicPolymorphicTypeValidator validator = BasicPolymorphicTypeValidator.builder() + .allowIfSubType("com.viewsh.") + .allowIfSubType("com.alibaba.fastjson2.JSONObject") + .allowIfSubType("com.alibaba.fastjson2.JSONArray") + .allowIfSubType("java.util.") + .allowIfSubType("java.time.") + .allowIfSubType("java.lang.") + .allowIfSubType("java.math.") + .allowIfSubTypeIsArray() + .build(); + mapper.activateDefaultTyping( + validator, + ObjectMapper.DefaultTyping.NON_FINAL, + JsonTypeInfo.As.PROPERTY); + return mapper; + } + @Bean("videoRedisTemplate") public RedisTemplate videoRedisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate redisTemplate = new RedisTemplate<>(); - // Using Jackson serializer as fallback (WVP originally used FastJSON2) - GenericJackson2JsonRedisSerializer jacksonSerializer = new GenericJackson2JsonRedisSerializer(); + GenericJackson2JsonRedisSerializer jacksonSerializer = + new GenericJackson2JsonRedisSerializer(buildRedisObjectMapper()); redisTemplate.setValueSerializer(jacksonSerializer); redisTemplate.setHashValueSerializer(jacksonSerializer); - // key serializer redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setConnectionFactory(redisConnectionFactory); @@ -38,7 +87,8 @@ public class RedisTemplateConfig { public RedisTemplate videoRedisTemplateForMobilePosition( RedisConnectionFactory redisConnectionFactory) { RedisTemplate redisTemplate = new RedisTemplate<>(); - GenericJackson2JsonRedisSerializer jacksonSerializer = new GenericJackson2JsonRedisSerializer(); + GenericJackson2JsonRedisSerializer jacksonSerializer = + new GenericJackson2JsonRedisSerializer(buildRedisObjectMapper()); redisTemplate.setValueSerializer(jacksonSerializer); redisTemplate.setHashValueSerializer(jacksonSerializer); redisTemplate.setKeySerializer(new StringRedisSerializer());