问题描述
我正在使用此 repo https://github.com/kojenov/serial/tree/master/3-4.%20upload 中的示例,该示例提供了一种方法,用于通过定义自定义 ObjectInputStream 并覆盖受保护方法 resolveClass 来指定保护 Java 中表单不安全反序列化的方法strong> 其中我们必须指定允许反序列化的类。 我的问题是我在 Planet 类中添加了一个 LocalDate 字段,当我反序列化一个序列化对象时,我得到了这个异常:
除不受支持的类之外的无效类; java.time.Ser
我在网上搜索,我找不到任何其他遇到该问题的情况,所以我真的很困惑。我尝试使用而不是 LocalDate 添加 LocalDateTime, 同样的错误再次发生。据我所知,java.time.Ser 类是该包中类层次结构中某处的受保护类。 LocalDate 类是可序列化的,所以这不应该发生。我确信问题出在 LocalDate 中,因为如果我使该字段 transient 代码按预期工作。我是遗漏了什么还是只是 Java 对象序列化的一个错误? 顺便说一下,这些例子最初来自 Alexei Kojenov 的演讲,他的网站是 kojenov.com,但我找不到他的电子邮件来亲自问他。
解决方法
序列化是递归的过程,这意味着当你序列化一个复杂的对象时,首先你需要序列化它的所有属性。反序列化也会发生同样的事情。
Planet
对象包含类型为 int
、double
和 java.lang.String
的字段,它们是原语,不需要特殊(反)序列化。 LocalDate
或 LocalDateTime
不是原语,它们被序列化,然后用 SafeObjectInputStream
反序列化。
序列化黑客
正如java.io.Serializable
documentation 中所说,对象可以修改它们的序列化行为,甚至可以通过定义方法writeReplace
将序列化委托给另一个类。
JavaDoc 引用:
在将对象写入流时需要指定要使用的替代对象的可序列化类应使用确切的签名实现此特殊方法:
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
如果该方法存在并且可以从正在序列化的对象的类中定义的方法访问,则此 writeReplace 方法由序列化调用。因此,该方法可以具有私有、受保护和包私有访问。对该方法的子类访问遵循 Java 可访问性规则。
LocalDate
和 LocalDateTime
都利用了这种可能性并定义了 writeReplace
方法。
例如,java.time.LocalDate
的实现:
private Object writeReplace() {
return new Ser(Ser.LOCAL_DATE_TYPE,this);
}
java.time.Ser
是包私有的 final 类,用作 java.time.*
对象的委托。
因此,当您序列化 java.time.LocalDate
或 java.time.LocalDateTime
时,实际上是在序列化 java.time.Ser
。
自定义解串器
之前我们发现 java.time.LocalDate
被序列化为 java.time.Ser
。现在,让我们尝试使用 SafeObjectInputStream
对其进行反序列化。
反序列化前调用resolveClass
方法:
@Override
protected Class<?> resolveClass(ObjectStreamClass input)
throws IOException,ClassNotFoundException
{
if (!input.getName().equals(Planet.class.getName())) {
throw new InvalidClassException("Unsupported class",input.getName());
}
return super.resolveClass(input);
}
它检查类名是否等于 Planet.class.getName()
,但 java.time.Ser
不等于,这就是您收到异常的原因。
解决方案
要解决此问题,您需要将 java.time.Ser
添加到受信任的类列表中。我建议下一步修改您的 SafeObjectInputStream
:
public class SafeObjectInputStream extends ObjectInputStream {
private final List<String> supportedClasses = List.of(Planet.class.getName(),"java.time.Ser");
public SafeObjectInputStream(InputStream inputStream) throws IOException {
super(inputStream);
}
@Override
protected Class<?> resolveClass(ObjectStreamClass input)
throws IOException,ClassNotFoundException
{
if (!supportedClasses.contains(input.getName())) {
throw new InvalidClassException("Unsupported class ",input.getName());
}
return super.resolveClass(input);
}
}
注意:List.of
是在 Java 9 中引入的。如果您的 Java 版本低于 9,您可以将其替换为 Arrays.asList
。