java 序列化过程浅析
字数 1881 2025-08-12 11:34:11
Java序列化与反序列化深度解析
一、序列化基础概念
1.1 定义
- 序列化:将数据结构或对象转换为字节流的过程,以便存储或传输
- 反序列化:将字节流还原为原始数据结构或对象的过程
1.2 序列化目的
- 网络传输:RMI、Web Services等远程通信
- 持久化存储:数据库、文件系统、缓存
- 认证令牌:如Shiro的RememberMe功能
1.3 Java序列化接口
Serializable:标记接口,无方法,表示类可被序列化Externalizable:继承自Serializable,需要手动实现序列化逻辑
二、Java序列化实现
2.1 基本序列化示例
// 可序列化类
public class Operation implements Serializable {
private final int a;
private final int b;
public Operation(int a, int b) {
this.a = a;
this.b = b;
}
public void add() {
System.out.println(a + "+" + b + "=" + (a+b));
}
}
// 序列化操作类
public class SerializationSample {
public static void serialize(Object obj, String path) throws IOException {
FileOutputStream fos = new FileOutputStream(path);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(obj);
oos.close();
}
public static Object deserialize(String path)
throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream(path);
ObjectInputStream ois = new ObjectInputStream(fis);
Object obj = ois.readObject();
ois.close();
return obj;
}
}
2.2 序列化数据格式
Java序列化流特征:
- 魔数:
aced(Base64特征为rO0AB) - 版本号:
0005
序列化数据组成结构:
- 魔数与版本号
- 类描述信息
- 字段数据
- 结束标记
2.3 SerialVersionUID
- 8字节长的类指纹信息,决定反序列化是否成功
- 未显式指定时,JVM会根据类信息自动生成
- 实际开发中建议显式声明,避免类变更导致兼容性问题
三、高级序列化特性
3.1 自定义序列化
通过重写以下方法实现自定义序列化逻辑:
// 在可序列化类中定义
private void writeObject(ObjectOutputStream out) throws IOException {
// 自定义序列化逻辑
out.defaultWriteObject(); // 默认序列化
}
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
// 自定义反序列化逻辑
in.defaultReadObject(); // 默认反序列化
}
3.2 对象替换
// 序列化前替换对象
private Object writeReplace() {
return replacementObject;
}
// 反序列化后替换对象
private Object readResolve() {
return replacementObject;
}
注意:
writeReplace和readResolve通常不同时使用writeReplace优先级高于readResolve
四、序列化安全与漏洞
4.1 反序列化漏洞原理
当重写readObject方法并执行危险操作时:
private void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
ois.defaultReadObject();
// 危险操作示例:动态加载字节码
Method defineMethod = ClassLoader.class.getDeclaredMethod(
"defineClass", String.class, byte[].class, int.class, int.class);
defineMethod.setAccessible(true);
Class exp = (Class) defineMethod.invoke(
ClassLoader.getSystemClassLoader(),
null, this.byteCode, 0, this.byteCode.length);
exp.newInstance();
}
4.2 常见反序列化触发点
| 类型 | 触发点 | 所在组件 |
|---|---|---|
| JDK内置 | ObjectInputStream.readObject | java.io.ObjectInputStream |
| JDK内置 | ObjectInputStream.readUnshared | java.io.ObjectInputStream |
| XML | XMLDecoder.readObject | java.beans.XMLDecoder |
| JSON | ObjectMapper.readValue | com.fasterxml.jackson.databind.ObjectMapper |
| JSON | JSON.parseObject | com.alibaba.fastjson.JSONObject |
| YAML | Yaml.load | org.yaml.snakeyaml.Yaml |
五、核心API解析
5.1 ObjectOutputStream
-
writeObject(Object obj)
- 公开方法,序列化入口
- 最终调用writeObject0(obj, false)
-
writeUnshared(Object obj)
- 与writeObject区别:unshared=true
- 相同对象不会共享引用,会完整序列化
-
writeObjectOverride(Object obj)
- 受保护方法,用于子类覆盖默认序列化行为
-
writeObject0(Object obj, boolean unshared)
- 私有方法,实际序列化实现
- 处理流程:
- 检查null对象
- 处理替换对象(writeReplace)
- 根据对象类型选择序列化方式
5.2 序列化调用链
writeObject
-> writeObject0
-> writeOrdinaryObject
-> writeSerialData
-> ObjectStreamClass.invokeWriteObject (通过反射调用自定义writeObject)
5.3 ObjectInputStream
与ObjectOutputStream对称,主要方法:
- readObject()
- readUnshared()
- readObjectOverride()
- readObject0()
六、防御与最佳实践
- 避免反序列化不可信数据
- 使用白名单验证反序列化类
- 重写readObject进行输入验证
- 使用transient关键字保护敏感字段
- 考虑使用替代序列化方案:
- JSON (Jackson, FastJson)
- XML
- Protocol Buffers
- Kryo
七、扩展学习方向
- Java反序列化漏洞利用链(CC链)分析
- 各组件反序列化机制深入研究:
- XMLDecoder
- XStream
- SnakeYAML
- Jackson
- FastJson
- 安全防护机制:
- JEP 290(JDK9+)
- 反序列化过滤器