可通过重写private void readObject(ObjectInputStream in)方法,在in.defaultReadObject()后插入校验逻辑,对反序列化字段进行安全检查;需配合transient、readResolve及禁用原生序列化等措施增强防护。

可以通过重写 readObject 方法,在反序列化过程中插入自定义校验逻辑,从而实现对字段值的安全检查与合法性控制。
重写 readObject 实现校验入口
Java 反序列化机制会自动查找类中是否定义了私有、签名严格匹配的 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException 方法。若存在,JVM 会在默认反序列化流程前调用它,此时对象尚未完全构建(字段仍为默认值),但可通过 in.defaultReadObject() 触发标准反序列化,再对已恢复的字段做校验。
- 必须声明为
private、void返回、且参数类型精确为ObjectInputStream - 必须在方法内显式调用
in.defaultReadObject(),否则字段不会被反序列化 - 校验代码应放在
defaultReadObject()之后,确保字段已被赋值
在 readObject 中执行字段校验
校验逻辑可覆盖类型安全、范围约束、格式合规、业务规则等维度。例如校验一个表示年龄的 int age 字段是否在合理区间:
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
if (age < 0 || age > 150) {
throw new InvalidObjectException("age must be between 0 and 150");
}
}
也可结合正则校验字符串字段(如邮箱、手机号)、检查引用对象非空、验证枚举值是否合法等。注意:此时 this 指向正在构造的对象实例,所有非 transient 字段已由 defaultReadObject 填充完毕。
配合 transient 和 readResolve 防绕过
仅靠 readObject 不足以防御全部攻击。需配合其他机制增强健壮性:
- 将敏感或易被篡改的字段声明为
transient,强制其不参与自动序列化,改由readObject中手动从流中读取并校验(如in.readInt()) - 对单例类或需控制实例唯一性的场景,补充
readResolve方法,返回指定实例,防止反序列化伪造新对象 - 避免在
readObject中调用可能被重写的方法(如普通 getter/setter),以防子类恶意覆写导致校验失效
注意兼容性与替代方案
该机制依赖 Java 原生序列化,而原生序列化本身存在严重安全风险(如 gadget chain 反序列化漏洞),生产环境应优先考虑禁用或替换:
- 禁用:通过 SecurityManager 或 JVM 参数限制反序列化目标类
- 替代:使用 JSON(Jackson/Gson)、Protobuf 等白名单驱动的序列化框架,天然规避
readObject攻击面 - 若必须用原生序列化,建议搭配
ObjectInputFilter(Java 9+)全局控制可反序列化类

















