Java ObjectInputFilter 怎样限制反序列化对象
来源:17golang原创
时间:2026-10-18 01:12:03 307浏览 收藏
我第一次把 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 级配置适合作为统一底线。实际项目通常需要两者配合,并把迁移到明确协议作为长期方向。
-
479 收藏
-
337 收藏
-
128 收藏
-
149 收藏
-
202 收藏
-
476 收藏
-
341 收藏
-
327 收藏
-
501 收藏
-
208 收藏
-
文章 · java教程 | 1小时前 | 字符集 · Java教程 · HTTP客户端 · java httpclient charset BodyHandlers ofByteArray ofString429 收藏
-
170 收藏
-
265 收藏
-
293 收藏
-
207 收藏
-
308 收藏
-
140 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习