RedisTemplate反序列化含多态对象属性的实例时抛出异常
问题根因
当前配置的Jackson序列化器没有开启多态对象的类型元数据写入能力:
- 序列化阶段,Jackson仅按照字段的静态声明类型(也就是
Father)生成JSON,不会把实际存入的Son子类的类标识、独有属性映射规则写入结果 - 反序列化阶段,Jackson拿到带
sonName属性的JSON片段,严格按照TargetObj中声明的Father类型解析,遇到未在Father类中定义的sonName字段,就会直接抛出无法识别的异常
解决方案
1. 调整RedisConfig中的ObjectMapper配置
在原有配置基础上,开启带安全校验的多态类型支持,让Jackson序列化时自动把对象的实际类名作为元数据写入JSON,反序列化时根据元数据还原成正确的子类实例。
针对Spring Boot 2.4.3内置的Jackson 2.11.x版本,使用非过时的API配置,避免旧版本enableDefaultTyping带来的安全漏洞:
- 首先配置类型校验规则,仅放行自己项目包下的类做反序列化,避免外部恶意类的反序列化攻击
- 激活多态默认类型规则,所有非final的对象类型序列化时自动携带
@class类型标记
调整后的完整RedisConfig代码如下:
@Configuration public class RedisConfig extends CachingConfigurerSupport { @Bean(name = "redisTemplate") public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); // 新增带安全校验的多态配置 BasicPolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder() .allowIfBaseType("com.hyc.backend.") // 替换为自己项目的实际根包路径 .build(); om.activateDefaultTyping( ptv, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY ); jackson2JsonRedisSerializer.setObjectMapper(om); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); redisTemplate.afterPropertiesSet(); return redisTemplate; } }
配置完成后,序列化Son实例生成的JSON片段会类似:
{"@class":"com.hyc.backend.dao.Son","fatherName":"fatherName","sonName":"sonName"}
反序列化时Jackson看到@class标记,就会直接还原成对应的Son实例,不会再硬按Father类型解析。
2. 修正测试代码的使用方式
原有测试代码里自己new了一个全新的ObjectMapper做convertValue转换,这个新的ObjectMapper没有配置上述多态规则,就算Redis里存储的JSON带了完整的类型元数据,也会触发同样的报错。
正确的测试逻辑应该直接使用RedisTemplate的内置序列化能力取出反序列化完成的对象,不需要自己手动新建ObjectMapper转换:
TargetObj to = new TargetObj(); to.setId(1); to.setName("toname"); Son son = new Son(); son.setFatherName("fatherName"); son.setSonName("sonName"); to.setProperty(son); redisTemplate.opsForValue().set("test", to); // 直接通过RedisTemplate取出已经反序列化完成的对象 TargetObj test = (TargetObj) redisTemplate.opsForValue().get("test"); // 此时property字段实际为Son类型,可正常获取所有子类属性 Son actualProperty = (Son) test.getProperty(); System.out.println(actualProperty.getSonName()); // 正常输出"sonName"
可选替代方案
如果不想全局开启多态序列化带来的JSON体积增大问题,可以针对需要支持多态的字段单独加Jackson注解,只让对应字段保留类型标记:
在TargetObj的property字段上添加注解:
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class") private Father property;
这种方式仅对单个字段生效,不需要调整全局ObjectMapper配置,适合多态场景比较少的项目。
注意事项
- 多态配置必须加安全校验规则,禁止使用无校验的全类放行配置,否则会存在远程反序列化的安全漏洞
- 如果类被
final修饰,NON_FINAL的默认规则不会为其添加类型标记,这类类不需要多态支持的话可以正常使用 - 调整配置后,需要清空之前存在Redis里的旧测试数据,旧数据没有携带类型标记,还是会触发反序列化报错
内容的提问来源于stack exchange,提问作者Kolin Huang
相关产品推荐
相关产品推荐

