February 26, 2024

java反序列化

前言

在现代软件开发中,数据的传输和存储是不可避免的需求。为了在不同的系统、语言和环境之间交换数据,开发者通常采用一些标准的格式,如 JSON 和 XML。然而,这些格式在处理复杂数据类型(如对象、集合等)时存在局限性。为了解决这个问题,许多语言和框架引入了序列化和反序列化机制,将对象转换为字节流,以便传输和存储。

序列化(Serialization):将对象的状态转换为字节流。

反序列化(Deserialization):将字节流恢复为对象的过程。

大多数处理方法中,JSON和XML支持的数据类型就是基本数据类型,整型、浮点型、字符串、布尔等,如果开发者希望在传输数据的时候直接传输一个对象,那么就不得不想办法扩展基础的JSON(XML)语法。

不管是Jackson、Fastjson还是编程语言内置的序列化方法,一旦涉及到序列化与反序列化数据,就可能会涉及到安全问题。

通过对比PHP的__wakeup和java的readobject的区别来深入理解java反序列化

readObject 倾向于解决“ 反序列化时如 何还原一个完整对象“这个问题,而PHP的 __wakeup 更倾向于解决“反序列化后如何初始化这个对象”的问题。

PHP反序列化

PHP的序列化是开发者不能参与的,开发者调用 serialize 函数后,序列化的数据就已经完成了,你得到的是一个完整的对象,你并不能在序列化数据流里新增某一个内容,你如果想插入新的内容,只有将其保存在一个属性中。也就是说PHP的序列化、反序列化是一个纯内部的过程,而其 __sleep 、__wakeup 魔术方法的目的就是在序列化、反序列化的前后执行一些操作。

总结:无法在序列化数据流中插入自定义内容,开发者不能直接干预序列化过程,只能通过对象的属性来影响序列化结果。

__sleep()

在序列化之前执行,返回一个数组,包含需要序列化的属性名。

1
2
3
4
5
6
7
8
9
10
11
<?php
class Connection {
public $link;

public function __sleep() {
// 在序列化之前关闭连接
$this->link = null;
return array('link');
}
}

__wakeup()

在反序列化之后执行,用于恢复对象的状态。

1
2
3
4
5
6
7
8
9
<?php
class Connection {
public $link;

public function __wakeup() {
// 在反序列化之后重新建立连接
$this->link = $this->connect();
}
}

资源类型的序列化问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class Connection {
protected $link;
private $dsn, $username, $password;

public function __construct($dsn, $username, $password) {
$this->dsn = $dsn;
$this->username = $username;
$this->password = $password;
$this->connect();
}

private function connect() {
$this->link = new PDO($this->dsn, $this->username, $this->password);
}
}

1
2
3
4
5
<?php
$conn = new Connection('mysql:host=localhost;dbname=test', 'root', 'password');
$serialized = serialize($conn);
echo $serialized;
// 输出的序列化数据中,$link 属性的值为 NULL

所以要sleep()返回三个参数,然后wakeup执行connect

java反序列化

Java反序列化的操作,很多是需要开发者深入参与的,所以你会发现大量的库会实现 readObject 、writeObject 方法,这和PHP中 __wakeup 、__sleep 很少使用是存在鲜明对比的。

其实很好理解

最大的区别就是java能够控制序列化的过程。

1
2
3
4
5
6
7
8
9
10
private void writeObject(java.io.ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
s.writeObject("This is a object");
}
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
String message = (String) s.readObject();
System.out.println(message);
}

3. objectAnnotation 的角色

在这段代码中,特别引人注意的是 objectAnnotation。在 Java 的序列化过程中,除了对象的字段信息之外,开发者还可以将额外的数据放入 objectAnnotation 区域中。这使得 Java 的序列化变得更加灵活。通过 writeObject 写入的 "This is a object" 就被存放在 objectAnnotation 中。

序列化生成的数据流内容展示了这一点:

image-20240925040709147

1
2
3
4
5
6
objectAnnotation
TC_STRING - 0x74
newHandle 0x00 7e 00 05
Length - 16 - 0x00 10
Value - This is a object - 0x546869732069732061206f626a656374
TC_ENDBLOCKDATA - 0x78

这里的 objectAnnotation 位置就是用于存储额外的序列化数据。在反序列化时,通过 readObject() 可以读取该区域的数据并进行相应的处理。


classAnnotation 主要用于描述类的额外信息,和类的元数据相关,开发者通常不会直接操作。

objectAnnotation 则用于存储对象级别的额外数据,开发者可以通过自定义 writeObjectreadObject 方法来操作这个区域。

简单例子

1

About this Post

This post is written by void2eye, licensed under CC BY-NC 4.0.

#Web