首页 >  文章 >  java教程

Java ObjectInputFilter 怎么拦截反序列化:按类名、数组长度与深度做白名单验收

来源:17golang原创

时间:2026-08-16 21:12:53 186浏览 收藏

老系统把订单快照存成 Java 序列化字节,升级接口后,最麻烦的不是“能不能读出来”,而是输入里混进了不属于这个模块的类,或者数组和对象引用规模远超正常订单。把数据读进 ObjectInputStream 之前加一层 ObjectInputFilter,可以先按类名和资源上限做一次拦截。

要点速览
  • 白名单过滤优先允许业务 DTO 和必要的 java.base 类型,其他类保持拒绝或未决,别只维护一份越来越长的黑名单。
  • FilterInfo 能提供当前类、数组长度、对象图深度、引用数和输入字节数,类名检查与资源限制要同时做。
  • createFilter 的模式用分号分隔,maxarraymaxdepth 等限制和类模式共同决定结果,顺序写错会让验收失真。
  • 过滤器是防御层,不等于可以放心读取不可信序列化数据;新接口优先选 JSON、协议缓冲等明确格式。

先画清楚:输入要过哪几道门

把一次订单快照读取拆成三步更容易验收:先判断当前类是否属于允许集合,再判断数组和对象图是否超过正常规模,最后才让 ObjectInputStream 继续读取。过滤器检查的是反序列化过程中的对象信息,不会提前替你检查每个字段的业务含义。

Java ObjectInputFilter 将订单快照输入经过类名和资源限制两道过滤后放行或拒绝

这里要特别注意边界:Oracle 文档明确提醒,不可信数据的反序列化本身就有风险。过滤器的价值是缩小可接受的类集合并限制对象图复杂度,不能把历史序列化格式变成一种天然安全的交换协议。

最小可用写法:只允许订单快照和基础类型

假设业务只保存 OrderSnapshotOrderLine 和少量集合数据。先用模式过滤器限制包范围,再把输入流绑定到这个过滤器:

import java.io.ObjectInputFilter;
import java.io.ObjectInputStream;
import java.nio.file.Files;
import java.nio.file.Path;

Object readSnapshot(Path file) throws Exception {
    var pattern = "com.example.order.**;java.base/*;maxarray=200;maxdepth=12;!*";
    var filter = ObjectInputFilter.Config.createFilter(pattern);

    try (var input = Files.newInputStream(file);
         var objects = new ObjectInputStream(input)) {
        objects.setObjectInputFilter(filter);
        return objects.readObject();
    }
}

java.base/* 让基础模块中的必要类型参与匹配,最后的 !* 把没有被前面模式覆盖的类拒绝掉。数组上限和深度上限不是装饰项:订单行数量异常、嵌套集合过深时,过滤器应当在读取过程中返回拒绝状态。

自定义检查:把“允许什么”和“最多多少”分开

模式字符串适合固定规则;如果还要记录拒绝原因,可以实现一个小型过滤器。返回 UNDECIDED 表示本过滤器暂不做决定,返回 ALLOWEDREJECTED 才是明确结论:

ObjectInputFilter orderFilter = info -> {
    Class> type = info.serialClass();
    if (info.arrayLength() > 200 || info.depth() > 12) {
        return ObjectInputFilter.Status.REJECTED;
    }
    if (type == null) {
        return ObjectInputFilter.Status.UNDECIDED;
    }
    if (type.getName().startsWith("com.example.order.")) {
        return ObjectInputFilter.Status.ALLOWED;
    }
    if (type.getModule().getName().equals("java.base")) {
        return ObjectInputFilter.Status.ALLOWED;
    }
    return ObjectInputFilter.Status.REJECTED;
};

数组检查不能只在 serialClass() 非空时执行,因为数组长度是过滤器单独提供的指标。对象图深度也应结合真实订单样本测出上限,直接照抄一个很小的数字,可能把合法的嵌套 DTO 一起挡掉。

威胁路径:黑名单为什么越补越被动

黑名单的思路是“遇到一个危险类就加一条拒绝规则”,但业务依赖、库版本和类加载范围一变,遗漏就会重新出现。白名单的边界更清楚:订单读取器只需要少数 DTO 和基础类型,未声明的类不应该因为“暂时没发现问题”而自动进入。

如果多个模块各自设置流级过滤器,还要审查 JVM 级过滤器和过滤器工厂的合并关系。Oracle 的过滤文档强调,组合过滤器遇到任一明确拒绝时应拒绝;不能在局部过滤器里简单返回新规则,反而绕开全局安全边界。

Java 反序列化过滤器对比无边界对象图与设置 maxarray maxdepth 后的受控对象图

用四组样本做验收,不看一次成功就收工

样本预期结果核对点
正常 OrderSnapshot允许读取业务包名、数组长度、深度都在范围内
未登记的业务类拒绝读取日志能定位到 serialClass
超长 OrderLine 数组拒绝读取arrayLength 超过 maxarray
多层嵌套快照拒绝读取depth 超过 maxdepth,业务侧保留失败记录

每组样本都要记录过滤器配置、Java 版本、结果状态和失败原因。不要只断言“抛了异常”,还要确认是输入类不允许、数组超限还是深度超限,否则规则改动后很难知道到底放宽了哪一道门。

上线前的安全清单

  • 能改协议就停止新增 Java 原生序列化入口,迁移到字段和版本都更明确的格式。
  • 必须兼容旧数据时,先盘点每个读取点的 DTO 白名单,再设置数组、深度、引用数和字节数上限。
  • 把过滤器拒绝日志接入告警,但不要把完整输入内容和敏感订单字段写进日志。
  • 升级 JDK、引入新 DTO 或新增集合字段后,重新跑四组边界样本。

相关问题

ObjectInputFilter 能让不可信序列化数据变安全吗?

不能。它是类和资源边界的防御控制,不能替代避免读取不可信数据、认证来源和迁移到更明确数据格式等措施。

ALLOWEDUNDECIDED 有什么区别?

ALLOWED 是当前过滤器明确放行,UNDECIDED 是当前过滤器不表态,组合过滤器或后续规则仍可能给出拒绝。

只设置 maxdepth 够不够?

通常不够。对象图深度、引用数、数组长度和输入字节数限制关注的是不同资源维度,应根据正常样本组合设置并用边界数据验证。

过滤器应该设置在全局还是单个流上?

全局规则适合统一底线,单个流适合更严格的业务白名单。多层规则并存时要确认组合逻辑没有绕开全局过滤器。

把过滤器当成协议边界来维护

ObjectInputFilter 最有用的地方,不是帮旧序列化代码“加一行安全配置”,而是把可读类集合、对象图规模和验收样本都明确下来。先收紧输入边界,再逐步迁移掉不必要的原生序列化,才是更稳妥的长期方案。可继续参考 OpenJDK JEP 290Oracle ObjectInputFilter APIOracle Serialization Filtering 指南

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