1089 字
约 3 分钟
8
S06-J02 · 序列化陷阱:版本兼容、循环引用与反序列化安全
适用读者:要把对象存进 Redis/文件、或做接口兼容的人。
前置:S06-B03。
实测环境:JDK 21(serialVersionUID实测,2026-11-05)。
覆盖旧文:—(E 驱动)
覆盖扩展:E-S06-009(版本兼容)、E-S06-010(反序列化安全)。
上一篇:S06-J01 · 多模块工程结构:parent / BOM / 聚合
下一篇:S06-J03 · 代码生成。
1. 一句话结论
序列化的所有麻烦都来自同一件事:类的定义会变、数据却已经存下来了。 所以要么"让数据自描述"(JSON),要么"严格管版本"(原生序列化)。
2. 实测:serialVersionUID 不一致会怎样(E-S06-009)
写入时 serialVersionUID = 1L:
1. 写入(UID=1):62 字节
用 serialVersionUID = 2L 读同一个文件:
结果: java.io.InvalidClassException / User; local class incompatible:
stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
| 情况 | 结果 |
|---|---|
| UID 相同、字段新增 | 能读(新字段为默认值) |
| UID 不同 | InvalidClassException |
| UID 不同但字段完全一致 | 仍然失败(UID 就是唯一判据) |
推论:显式声明 serialVersionUID 是"承诺"——承诺后,字段变化由你负责兼容;不声明则每次编译可能生成不同 UID,跨版本必然失败。
3. 版本兼容的三种策略
| 策略 | 做法 | 适用 |
|---|---|---|
| 向后兼容(新读旧) | 新字段给默认值 | 常见 |
| 向前兼容(旧读新) | 旧版本忽略未知字段 | JSON 天然支持 |
| 强制升级 | UID 变更,旧数据作废 | 数据结构大改 |
JSON 的优势:字段名自描述,旧代码读新数据只忽略多余字段(配置 FAIL_ON_UNKNOWN_PROPERTIES=false)。这也是"对外接口用 JSON"的原因之一。
4. 反序列化安全(E-S06-010)
Java 原生反序列化 = 把字节流转成对象,且会执行 readObject 等方法——这意味着攻击者可以构造字节流触发任意代码。
| 风险 | 说明 |
|---|---|
| Gadget 链 | 利用库中已有的 readObject 组合执行命令 |
| 类加载 | 反序列化会加载并初始化类 |
| 历史事件 | 多个知名框架因此爆过 RCE |
防护四条:
| 措施 | 说明 |
|---|---|
| 不用原生序列化接收不可信数据 | 首选(最有效) |
ObjectInputFilter(JDK 9+) |
白名单类 |
| 只接收可信来源(内网/签名) | 边界控制 |
| 用 JSON + 严格类型 | 减少攻击面(但注意多态类型 enableDefaultTyping 也是风险) |
判据:"这份字节流会从外部来吗?" 会 → 不要用原生反序列化。
5. 循环引用与懒加载(补 B03)
| 问题 | 现象 | 解法 |
|---|---|---|
| 循环引用 | JsonMappingException |
@JsonIgnore / DTO |
| JPA 懒加载 | 序列化时触发额外查询或 LazyInitializationException |
DTO + 显式字段 |
| 大对象 | 序列化后体积暴涨 | 只暴露必要字段 |
6. 五个误区
| 误区 | 真相 |
|---|---|
| "UID 相同就能随便改字段" | 新增可(默认值),删除/改类型要评估 |
| "不写 UID 更省事" | 每次编译可能不同 → 跨版本失败 |
| "反序列化只是解析数据" | 可能执行代码(RCE) |
| "JSON 就绝对安全" | 多态类型配置不当同样危险 |
| "序列化实体直接返回最方便" | 泄露结构、触发懒加载 |
7. 自检
- UID 不同但字段一致,能反序列化吗?
- 不写 UID 的风险是什么?
- 为什么 JSON 天然比原生序列化"向前兼容"?
- 原生反序列化为什么可能 RCE?四条防护里哪条最有效?
- 循环引用有哪三种解法?
8. 下一篇
S06-J03 · 代码生成:注解处理器与脚手架
序列化讲完,最后一篇进阶层:生成代码该不该进版本库、Lombok 与脚手架工具的边界。
本篇属于系列《S06 · Java 工程化与组件》。扩展点 E-S06-009/010。InvalidClassException 为 JDK 21 实测。
S06-J02 · 序列化陷阱:版本兼容、循环引用与反序列化安全
https://www.clxhxhhr.top/posts/5262/ 评论
0 条
还没有评论,先写一条吧。