使用 LocalDate 字段读取对象时自定义 ObjectInputStream 的意外行为 序列化黑客自定义解串器解决方案

问题描述

我正在使用此 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 对象包含类型为 intdoublejava.lang.String 的字段,它们是原语,不需要特殊(反)序列化。 LocalDateLocalDateTime 不是原语,它们被序列化,然后用 SafeObjectInputStream 反序列化。

序列化黑客

正如java.io.Serializable documentation 中所说,对象可以修改它们的序列化行为,甚至可以通过定义方法writeReplace 将序列化委托给另一个类。

JavaDoc 引用:

在将对象写入流时需要指定要使用的替代对象的可序列化类应使用确切的签名实现此特殊方法:

ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;

如果该方法存在并且可以从正在序列化的对象的类中定义的方法访问,则此 writeReplace 方法由序列化调用。因此,该方法可以具有私有、受保护和包私有访问。对该方法的子类访问遵循 Java 可访问性规则。

LocalDateLocalDateTime 都利用了这种可能性并定义了 writeReplace 方法。

例如,java.time.LocalDate 的实现:

private Object writeReplace() {
    return new Ser(Ser.LOCAL_DATE_TYPE,this);
}

java.time.Ser 是包私有的 final 类,用作 java.time.* 对象的委托。

因此,当您序列化 java.time.LocalDatejava.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