Java默认序列化不安全,因自动保存所有非transient字段并绕过构造器反序列化;自定义writeObject仅用于控制序列化内容、排除敏感字段,需配合transient和readObject使用,真正安全应改用JSON等格式或JDK序列化过滤器。

Java 中自定义 writeObject 的核心目的不是“实现安全序列化”,而是**控制序列化过程、排除敏感字段、执行自定义逻辑**;但若使用不当,反而会引入反序列化漏洞。真正的“安全序列化”需结合防御性设计与运行时防护,不能仅靠重写 writeObject 保证。
为什么默认序列化不安全?
Java 原生序列化(java.io.Serializable)会自动保存对象所有非 transient、非 static 字段,包括密码、令牌、连接句柄等敏感数据;更严重的是,反序列化时会绕过构造方法直接创建对象,并可能触发恶意代码(如重写 readObject 或利用 gadget 链)。自定义 writeObject 本身不阻止反序列化攻击,只是让你有机会“少写点东西”。
正确自定义 writeObject:只写必要字段
在类中声明私有方法 private void writeObject(ObjectOutputStream out) throws IOException,JVM 序列化时会优先调用它。你需要手动调用 defaultWriteObject()(写入非 transient 字段)或完全跳过它,改用 out.writeXXX() 写入白名单字段。
Java开发手册规约集合,基于阿里巴巴Java开发手册(嵩山版)。 涵盖7大维度:编程规约、异常日志、单元测试、安全规约、MySQL数据库、工程结构、设计规约。 当用户需要:(1) 编写或审查Java代码 (2) 检查命名/代码规范 (3) 处理异常和日志 (4) 编写单元测试 (5) 安全编码 (6) 数据库设...
-
推荐方式:显式写出关键字段,忽略敏感字段
把真正需要持久化的字段逐个写出,不依赖defaultWriteObject() -
避免混合使用:
defaultWriteObject()+ 手动写
容易遗漏 transient 字段的逻辑,且仍可能泄漏未标记为transient的敏感字段 - 示例(安全写法):
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject(); // ❌ 不推荐:仍会写所有非 transient 字段
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeInt(id); // ✅ 只写业务必需字段
out.writeUTF(username); // ✅ 字符串需用 writeUTF(兼容性好)
// 不写 password、sessionToken、dbConnection 等
}
配套必须做的事:readObject 和 transient 缺一不可
writeObject 必须与 readObject 成对出现,否则反序列化时字段无法正确恢复;同时,所有不应序列化的字段应明确声明为 transient,这是第一道防线。
立即学习“Java免费学习笔记(深入)”;
- 将敏感字段声明为
transient(如private transient String apiKey;) - 在
readObject中重置瞬态字段(如重新初始化、设为 null 或默认值) - 在
readObject开头校验输入流(可选但强烈建议):if (getClass() != theExpectedClass) throw new InvalidClassException(...);
真正提升序列化安全的替代方案
比起修补 Java 原生序列化,更安全的做法是放弃它:
-
用 JSON / Protobuf / CBOR 等格式替代:它们天然不执行代码,字段可控,生态成熟(如 Jackson +
@JsonIgnore) -
启用 JDK 17+ 的序列化过滤器(
jdk.serialFilter):全局限制可反序列化的类名模式 -
禁用反序列化(最彻底):系统不接收任何
ObjectInputStream输入,尤其避免从网络、文件、数据库读取原始字节流 -
使用
SerialFilterAPI(JDK 9+)动态配置白名单:比 JVM 参数更灵活

















