登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  java教程

Java 序列化边界怎么收紧:白名单与替代格式

来源:17golang原创

时间:2026-10-08 20:01:27 278浏览 收藏

Java 序列化边界要收紧,核心不是给 ObjectInputStream 再包一层异常,而是先限制“能读哪些类”,再限制“对象图能长多大”,最后把跨服务和不可信输入迁移到显式数据格式。只要输入不是完全受控的内部存储,就不应把原生反序列化当成通用消息协议。

要点速览
  • ObjectInputFilter 同时可以约束类名、数组长度、引用数、深度和输入字节数。
  • 白名单要覆盖业务 DTO 及其确实需要的 java.base 类型,不能用过宽的业务包通配。
  • 新接口优先使用带版本的 DTO 格式,迁移时保留可观测的旧格式读路径,并设置明确的下线条件。

本文依据 Java 官方序列化过滤文档和 ObjectInputStream API 说明边界;官方地址:https://docs.oracle.com/en/java/javase/25/core/java-serialization-filters.html。

先按输入来源划出三层边界

可信的本地快照、同一版本服务生成的内部缓存,可以暂时保留原生序列化,但读取入口仍应有资源上限。来自 HTTP、消息队列、文件上传、插件或跨团队服务的数据,应视为不可信;最稳妥的做法是拒绝原生反序列化,改用只承载业务字段的 DTO。不要把“加了过滤器”理解成“输入已经安全”,过滤器只是在对象恢复前增加一道允许/拒绝判断。

Java 序列化输入边界、过滤器与 DTO 领域边界的静态结构说明图
图1:序列化边界说明图,展示输入来源、过滤器和业务 DTO 的静态关系,不是运行截图。

建议把每个反序列化入口登记成一条策略:来源、允许的根对象、允许的包、最大深度、最大引用数、最大数组长度和最大字节数。这样异常日志里的 InvalidClassException 或过滤器拒绝原因,才能对应到具体业务入口,而不是只看到一条笼统的“反序列化失败”。

用白名单和资源上限保护 ObjectInputStream

简单场景可以使用 JVM 范围的模式过滤器;入口差异明显时,优先在每个流上设置更窄的过滤器。下面的模式只允许业务 DTO 和基础模块类型,同时限制对象图资源。!* 是拒绝其余类的关键边界,实际项目应把允许列表改成自己的稳定包名。

import java.io.ObjectInputFilter;
import java.io.ObjectInputStream;

// 只给受控 DTO 和必要的基础类型开门,同时限制对象图规模。
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
    "maxdepth=12;maxrefs=2000;maxbytes=1048576;maxarray=10000;"
        + "com.example.order.dto.*;java.base/*;!*"
);

try (ObjectInputStream input = new ObjectInputStream(source)) {
    // 必须在第一次 readObject 前绑定过滤器,避免读取后再补策略。
    input.setObjectInputFilter(filter);
    OrderSnapshot snapshot = (OrderSnapshot) input.readObject();
    // 反序列化只恢复 DTO;业务校验仍在这里或更靠后的服务层完成。
    return snapshot;
} catch (java.io.InvalidClassException ex) {
    // 被拒绝的类或资源上限属于输入失败,记录入口标识后丢弃该流。
    throw new IllegalArgumentException("serialized input rejected", ex);
}

类名白名单和资源上限是两件事:前者阻止不需要的类型进入对象图,后者限制深度、引用数量、数组长度与字节消耗。即使业务对象本身在白名单内,也可能因为嵌套集合过深或输入过大而被拒绝。setObjectInputFilter 必须在读取对象前调用,而且过滤器不是业务字段校验器,日期范围、租户归属和状态转换仍要在 DTO 校验层处理。

用显式格式替代类路径驱动的跨服务协议

原生序列化把类名、字段演进和加载器关系带进载荷,跨服务升级时很容易变成隐式耦合。面向人工排查或开放接口,可采用 JSON;面向稳定 schema 和高吞吐二进制载荷,可采用 Protocol Buffers;需要二进制但保留较灵活结构时,可评估 CBOR。无论选哪一种,都让 DTO、版本字段和未知字段策略成为协议的一部分。

Java 原生对象图与显式 DTO 格式契约的静态关系说明图
图2:格式迁移结构图,对比类路径驱动的对象图与带版本 DTO 契约的静态边界,不是运行截图。

迁移不要一次性删除旧格式。可以先让新生产者写入显式格式,消费者保留旧格式只读并统计命中;当旧流量降到可接受范围,再把旧格式入口隔离到一次性迁移工具,最后删除服务运行时的 ObjectInputStream。双写期间要保证字段默认值、枚举未知值、时间精度和空值语义一致,避免“格式换了但业务含义悄悄变了”。

上线前用清单验收安全与兼容

  • 来源清单:每个读取入口是否标明可信来源、根对象和责任服务。
  • 过滤清单:是否有明确白名单;深度、引用、数组和字节上限是否来自样本而不是拍脑袋。
  • 异常清单:被拒绝的类、超限输入和格式损坏是否有脱敏日志、指标和限流策略。
  • 迁移清单:新旧格式的回放样本、版本兼容、回滚开关和旧入口下线时间是否写入发布记录。

最容易忽略的是把 java.base/* 当成万能信任。它只代表基础模块中的类型允许参与恢复,不代表业务对象可信,也不替代 DTO 字段校验。上线后应持续观察拒绝率、输入大小分布和旧格式命中率,按入口调整策略,而不是为了减少告警把白名单扩大到整个业务包。

相关问题

只配置 jdk.serialFilter 就够了吗?

它可以提供 JVM 范围的基线,但多个业务入口的允许类型不同,仍应补充流级或上下文级策略;配置也必须在部署参数中可追踪。

过滤器拒绝后能否继续读取同一个流?

不要把失败流交回正常业务路径。官方 API 将读取异常视为流状态不确定,应关闭并丢弃,再按入口记录原因。

换成 JSON 就不需要校验了吗?

不是。显式格式降低了类路径耦合,但仍要做大小限制、字段校验、版本兼容、权限和业务语义校验。

参考资料:https://docs.oracle.com/en/java/javase/25/core/java-serialization-filters.html;https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/io/ObjectInputStream.html;https://openjdk.org/jeps/290。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>