java反序列化默认允许任意类加载,存在远程代码执行风险;jdk9起推荐使用objectinputfilter白名单机制,需显式设置且规则须在readobject前绑定,支持静态pattern或自定义实现,低版本需重写resolveclass,全局jdk.serialfilter不可作为主防线。

Java 反序列化默认允许加载任意类,一旦攻击者控制输入流(比如 HTTP body、Redis 数据、MQ 消息),就可能触发恶意类的 readObject 或构造逻辑,造成远程代码执行。从 JDK 9 开始,ObjectInputFilter 是官方推荐且最有效的白名单防御机制——它必须显式设置,否则等于完全放行。
用 Config.createFilter 设置静态白名单规则
这是最常用、最简洁的方式,适合绝大多数业务场景。核心是构造一条语义明确、限制严格的规则字符串,并在 readObject() 前绑定到 ObjectInputStream 实例上:
- 规则必须写在
new ObjectInputStream(...)之后、readObject()之前;晚于这个时机设置无效 - 使用
ObjectInputFilter.Config.createFilter("pattern")创建过滤器,不要传null(等同于全开) - 通配符
*只匹配当前包一级,子包需用**;例如com.example.dto.*不包含com.example.dto.sub.User,应写成com.example.dto.** - 规则顺序有优先级:
!*放最后兜底,允许项写在前面;以!开头的拒绝规则优先级更高 - 建议搭配基础防护参数:如
maxdepth=5(对象图嵌套深度)、maxrefs=100(引用次数)、maxarray=50000(数组长度),防 DoS 但不能替代类白名单
示例:
ObjectInputStream ois = new ObjectInputStream(inputStream);<br>ois.setObjectInputFilter(ObjectInputFilter.Config.createFilter(<br> "java.lang.String;java.util.ArrayList;com.example.model.**;maxdepth=5;maxrefs=100;maxarray=50000"<br>));
自定义 ObjectInputFilter 实现动态校验
当白名单需运行时决定(如按租户/请求头切换)、或需审计日志、或需检查类是否为 record / 是否含危险方法时,内置 pattern 无法满足,应实现 ObjectInputFilter 接口:
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
- 重写
checkInput(FilterInfo info)方法,返回ALLOWED、REJECTED或UNDECIDED - 通过
info.serialClass()获取待反序列化的类;注意该值可能为null(如反序列化数组元素类型时),应先判空并返回REJECTED - 避免用
Class.forName()触发类加载——只用getName()或getPackageName()做字符串判断 - 白名单类名建议缓存到
Set<string></string>中,用contains()快速查找,比正则更安全高效
示例片段:
ObjectInputFilter filter = info -> {<br> Class> clazz = info.serialClass();<br> if (clazz == null) return ObjectInputFilter.Status.REJECTED;<br> String name = clazz.getName();<br> if (name.startsWith("java.lang.") || name.startsWith("com.example.dto.")) {<br> return ObjectInputFilter.Status.ALLOWED;<br> }<br> return ObjectInputFilter.Status.REJECTED;<br>};<br>ois.setObjectInputFilter(filter);
低版本 JDK(8 及以下)或特殊场景必须重写 resolveClass
JDK 9 以下没有 ObjectInputFilter,且某些框架(如 RMI、老版 Spring)内部会绕过 filter,此时唯一可靠方式是继承 ObjectInputStream 并重写 resolveClass(ObjectStreamClass desc):
- 必须重写该方法,不能只靠外部拦截——因为类加载实际发生在这里
- 从
desc.getName()提取类名做白名单判断,禁止调用Class.forName() - 非法类名必须抛
ClassNotFoundException;抛InvalidClassException可能被某些反序列化链绕过 - 可结合配置中心或数据库动态加载白名单,支持租户隔离等复杂策略
避免依赖全局 jdk.serialFilter 参数
通过 JVM 参数 -Djdk.serialFilter 或 System.setProperty("jdk.serialFilter", "...") 设置全局过滤器看似省事,但存在明显缺陷:
- 第三方库或框架可能自行创建
ObjectInputStream实例,绕过全局配置 - 规则难以精细化控制(如不同接口需不同白名单)
- 一旦配置错误,可能影响整个应用,排查成本高
- 不能做运行时决策(如基于用户权限动态放行)
因此,全局 filter 只宜作为兜底手段,不可作为主防线。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










