Java ObjectInputFilter 怎样限制反序列化对象
我第一次把 Java 的对象流过滤加到旧模块时,最容易混淆的是“限制类”与“验证业务数据”这两件事。ObjectInputFilter 能在对象被读入时检查类名、数组长度、引用数、对象图深度和累计字节数,但它不会把不可信的 Java 原生序列化变成通用的数据交换协议。更稳妥的做法是优先换成明确的 JSON、协议缓冲区等格式;确实需要兼容对象流时,再为每个读取入口补上边界。
本文用一个小实验把边界落到代码上:先限制允许出现的类,再给对象图设置资源上限,最后解释 ALLOWED、REJECTED 和 UNDECIDED 应该怎样组合。
先把 ObjectInputFilter 放在正确的位置
ObjectInputStream 负责从字节流恢复对象;过滤器是读取过程中的一道检查边界。它看到的不是业务字段校验结果,而是当前对象图的结构信息,例如正在读取的类、数组长度、引用数量、深度和已经消费的字节数。
如果入口仍然必须读取 Java 原生序列化,先设置流级过滤器,再把允许的类和资源上限写清楚;不要只写一个宽泛的黑名单,也不要把过滤器当成业务数据校验器。

这张图里最值得记住的是两个分组:类名规则决定“可以出现什么类型”,资源限制决定“对象图最多占用多少结构预算”。两者共同作用时,仍然应该让输入来源、协议格式和业务字段校验各自承担清晰职责。
为单个 ObjectInputStream 设置过滤器
先准备一个只在内存中读写的小对象。示例的重点不是对象内容,而是过滤器如何附着到具体的 ObjectInputStream。过滤器必须在第一次读取对象前设置。
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputFilter;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
record UserNote(String owner, String text) implements Serializable {}
public class FilterDemo {
public static void main(String[] args) throws Exception {
byte[] payload;
try (var bytes = new ByteArrayOutputStream();
var output = new ObjectOutputStream(bytes)) {
// 只构造演示输入;生产系统不要把不可信字节流直接当作可信对象。
output.writeObject(new UserNote("demo", "hello"));
output.flush();
payload = bytes.toByteArray();
}
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
"FilterDemo$UserNote;java.base/*;maxdepth=8;maxrefs=32;maxbytes=4096;!*"
);
try (var input = new ObjectInputStream(new ByteArrayInputStream(payload))) {
// 必须在 readObject 前设置,避免先读入对象再补限制。
input.setObjectInputFilter(filter);
Object value = input.readObject();
System.out.println(value.getClass().getName());
}
}
}
模式字符串按顺序表达允许项和资源上限,最后的 !* 让没有明确匹配的类进入拒绝分支。这里的类名要按实际编译后的名称填写;如果把嵌套类、代理类或集合类型加入允许集合,应该逐项说明原因,不要为了“先跑起来”直接使用无限制的通配符。
对我来说,最实用的检查点是:过滤器附着在每个读取入口,而不是只在某个工具类里创建一次;setObjectInputFilter 发生在 readObject() 之前;资源上限也根据真实对象图调整,而不是照抄一个看起来很大的数字。
用程序化过滤器表达更细的边界
模式字符串适合稳定的类名集合和通用上限。遇到数组大小、引用数或某类需要特殊判断的场景,可以直接实现 ObjectInputFilter。过滤器返回的是状态,不是业务异常:拒绝时由对象流终止读取,继续判断则保留组合空间。
ObjectInputFilter limits = info -> {
// 类名可能为空;资源指标则用来限制对象图的整体规模。
Class> serialClass = info.serialClass();
if (serialClass != null && serialClass != UserNote.class
&& serialClass != String.class) {
return ObjectInputFilter.Status.REJECTED;
}
if (info.depth() > 8 || info.references() > 32
|| info.streamBytes() > 4096) {
return ObjectInputFilter.Status.REJECTED;
}
// 没有触发本地规则时交给组合中的其它过滤器继续判断。
return ObjectInputFilter.Status.UNDECIDED;
};
try (var input = new ObjectInputStream(new ByteArrayInputStream(payload))) {
// 程序化过滤器同样必须在第一次读取前设置。
input.setObjectInputFilter(limits);
Object value = input.readObject();
}

代码中没有把所有情况都返回 ALLOWED,是因为“我没有发现问题”不等于“输入一定应该放行”。REJECTED 表示明确拒绝,ALLOWED 表示当前规则明确允许,UNDECIDED 表示把决定留给组合中的另一层。若团队要采用组合过滤器,应该把每层的责任写在配置说明里。
读懂三种 Status,避免把过滤器写反
| Status | 适合表达的意思 | 使用时要注意 |
|---|---|---|
ALLOWED | 当前规则明确接受这个类或资源状态 | 不要因为类名匹配就忽略深度、引用数和字节预算 |
REJECTED | 当前规则明确不接受,读取应停止 | 拒绝原因应能从日志或配置映射回具体边界 |
UNDECIDED | 本层没有结论,交给其它过滤器继续判断 | 不能把它误读成“已经安全放行” |
如果过滤规则既要允许业务类,又要保留资源上限,常见做法是先用类名模式收窄范围,再用程序化过滤器补充上限和特殊类型判断。每增加一层,都要确认组合后的最终状态,而不是只看其中一段配置。
什么时候放到 JVM 级配置
Oracle 文档还提供了 jdk.serialFilter 和过滤器工厂等 JVM 级入口。它们适合为一组应用统一提供最低限度的保护边界,但不应替代每个业务读取点的明确协议设计。JVM 级配置通常由启动参数或安全属性注入,使用前要把应用中实际需要的类集合和对象规模记录下来。
官方 API 说明:
https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/io/ObjectInputFilter.htmlhttps://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/io/ObjectInputStream.html
如果过滤器已经拒绝了输入,排查顺序可以固定成三步:先确认是类名规则还是资源上限触发;再对照对象图的实际类型和大小;最后决定是修正格式、调整边界,还是让该入口停止读取这类数据。不要通过不断放宽 !* 或取消上限来掩盖协议不清的问题。
我会保留的最小检查清单
- 不可信输入是否仍在使用 Java 原生序列化;能否迁移到明确的数据格式。
- 每个
ObjectInputStream是否在第一次读取前设置了过滤器。 - 允许类是否是最小集合,是否误用了无限制通配符。
- 深度、引用数、数组长度和字节数是否都有符合业务的上限。
- 团队是否记录了
ALLOWED、REJECTED、UNDECIDED各自的组合责任。
这套写法适合需要暂时兼容对象流的旧系统:边界清楚、改动集中,也方便后续迁移。新系统如果可以选择协议,我仍然会优先选结构明确、字段可审计的格式,再把 ObjectInputFilter 当作兼容期的防线,而不是长期的数据模型。
相关问题
ObjectInputFilter 能代替 JSON 字段校验吗?
不能。它主要检查类和对象图资源指标;业务字段的取值、必填关系和版本兼容仍然需要由数据格式与业务代码负责。
为什么只返回 UNDECIDED 不等于允许?
因为它代表当前过滤器没有结论,组合中的其它过滤器仍可能拒绝;只有明确返回 ALLOWED 才表达本层的放行意图。
流级过滤器和 JVM 级过滤器怎么选?
流级过滤器更贴近单个入口和业务对象;JVM 级配置适合作为统一底线。实际项目通常需要两者配合,并把迁移到明确协议作为长期方向。
Java JDBC 怎样读取批量插入生成键
- 上一篇
- Java JDBC 怎样读取批量插入生成键
- 下一篇
- Java 怎样用 jcmd 分析本地内存增长
-
- 文章 · java教程 | 1小时前 | Java · JVM · java JVM内存 jcmd Native Memory Tracking
- Java 怎样用 jcmd 分析本地内存增长
- 476浏览 收藏
-
- 文章 · java教程 | 1小时前 | 数据库 · jdbc · Java教程 · java jdbc 批量插入 PreparedStatement 生成键 getGeneratedKeys
- Java JDBC 怎样读取批量插入生成键
- 341浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Java Stream 怎样合并两路聚合结果
- 327浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Java 时间格式兼容多个输入格式的方法
- 501浏览 收藏
-
- 文章 · java教程 | 1小时前 | Java ·
- Java 结构化并发怎样处理子任务失败
- 208浏览 收藏
-
- 文章 · java教程 | 1小时前 | 字符集 · Java教程 · HTTP客户端 · java httpclient charset BodyHandlers ofByteArray ofString
- Java HttpClient 怎样按字节和字符串选择响应处理器
- 429浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Java Optional.stream 怎样把可选值接入 Stream 管道
- 170浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Java sealed interface 跨包继承限制怎样理解
- 265浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Java record 紧凑构造器怎样校验参数
- 293浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Java computeIfAbsent 递归更新时怎样避免死循环
- 207浏览 收藏
-
- 文章 · java教程 | 1小时前 | Java教程 · 接口设计 · java 有序集合 SequencedCollection reversed 首尾元素
- Java 有序集合怎样统一访问首尾元素
- 308浏览 收藏
-
- 文章 · java教程 | 11小时前 | Java · 并发编程 · 虚拟线程 · java future ExecutorService 虚拟线程
- Java 虚拟线程执行器的生命周期管理
- 140浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 510次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 570次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 596次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 548次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 364次使用
-
- Go Java 算法之字符串解码示例详解
- 2023-01-07 479浏览
-
- Go Java算法之单词搜索示例详解
- 2022-12-30 337浏览
-
- Gojava算法之括号生成示例详解
- 2023-02-22 128浏览
-
- GoJava算法之累加数示例详解
- 2023-01-07 149浏览
-
- GoJava算法最大单词长度乘积示例详解
- 2023-01-12 202浏览
