Java反序列化中,需在readObject方法内先调用defaultReadObject()再校验字段,否则字段仍为默认值;校验应覆盖范围、非空、格式、逻辑一致性及引用完整性,并配合transient和readResolve防范绕过,但原生序列化风险高,生产环境应禁用或改用更安全方案。

在 Java 反序列化过程中,通过重写 private void readObject(ObjectInputStream in) 方法,可以在对象字段完成还原后立即执行自定义校验,从而拦截非法或不一致的状态。这不是被动捕获异常,而是主动控制反序列化流程的关键入口。
校验必须放在 defaultReadObject() 之后
readObject 方法中,JVM 不会自动填充字段——你必须显式调用 in.defaultReadObject(),否则所有非 transient 字段仍为默认值(如 0、null、false)。只有在这行之后,this 上的字段才真正被赋值,校验才有意义。
- 错误做法:在校验逻辑里直接访问 age 或 name,但没调用 defaultReadObject() → 值全是默认值,校验无意义
- 正确顺序:先
in.defaultReadObject(),再检查if (age 150)或if (username == null || username.trim().isEmpty()) - 校验失败时抛 InvalidObjectException,JVM 会终止反序列化并丢弃该对象
校验覆盖常见合法性维度
字段值是否符合业务语义,不能只靠类型系统保证。readObject 是最后一道可控防线:
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
- 范围约束:比如年龄、金额、状态码是否越界
-
非空与格式:邮箱用正则匹配
^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$,手机号校验长度和数字组成 - 逻辑一致性:start 时间不能晚于 finish;枚举字段值是否在预定义集合内(避免反序列化出非法 ordinal)
- 引用完整性:若字段是对象引用(如 List 或自定义类),检查是否为 null,必要时做防御性拷贝(尤其涉及 mutable 类型如 Date、ArrayList)
配合 transient 和 readResolve 防绕过
单靠 readObject 不足以应对所有篡改场景。攻击者可能通过字节流直接修改字段值,或利用反序列化机制伪造多个实例:
立即学习“Java免费学习笔记(深入)”;
- 将敏感字段(如 password、token)声明为 transient,使其不参与默认序列化;在 readObject 中手动读取(如
password = (String) in.readObject()),并立即校验 - 对单例类,补充 private Object readResolve() 方法,返回唯一合法实例,防止反序列化构造新对象破坏单例契约
- 避免在 readObject 中调用普通 getter/setter 或外部方法(如数据库查询、HTTP 请求),这些可能被子类重写或引入不可控副作用
注意原生序列化的根本风险
readObject 校验虽有效,但前提是信任整个反序列化流程。Java 原生序列化协议本身缺乏白名单机制,容易被 gadget chain 利用执行任意代码。生产环境应:
- 禁用原生反序列化:通过 JVM 参数
-Dsun.misc.URLClassPath.disableJarChecking=true或 SecurityManager 限制可反序列化类 - 优先替换为更安全的序列化方案:如 JSON(Jackson/Gson,配合 @JsonCreator + 构造器校验)、Protocol Buffers、Avro 等
- 若必须用 Serializable,确保 serialVersionUID 显式声明,避免版本不兼容导致静默失败

















