0x01 前言

在Java中实现对象反序列化非常简单
只需要继承并实现 java.io.Serializable 或是 java.io.Externalizable 接口即可被序列化

其中 java.io.Externalizable 只是实现了 java.io.Serializable 的接口
但是两者还是有区别的,区别如下:
注意: 复制了P牛的小圈子一个大佬发的结论,比我自己总结的更好, 总的来说,有点小小修改

  1. 实现 Externalizable, 序列化过程需要开发人员自己实现。未实现 writeExternal()readExternal(),则序列化时不会保存和读取任何字段。属性使用和不使用 transient 修饰,无任何区别
  2. Externalizable 优先级比 Serializable 更高
  3. 如果在序列化的时候使用了构造方法的情况下,那Externalizable 必须要创建默认的无参构造方法, Serializable 方法可以没有默认的无参构造方法

    java.io.Externalizable 的例子讲解,下一篇在讲, 本文请聚焦在 java.io.Serializable

其中第三点,需要在在在注意一下,使用 Externalizable 的时候:
如果你在序列化的时候没有使用构造方法!!!!!
那么是可以不创建无参构造方法也能成功反序列化的
但是如果你使用了构造方法来进行序列化!!!!!
那么则必须创建一个无参构造方法,这样才能成功的反序列化

反序列化类对象时有如下限制:

  1. 被反序列化的类必须存在
  2. serialVersionUID 值必须一致

注意: 修改,添加,删除被序列化类的方法或是属性都会导致 serialVersionUID 值的变化

另外需要注意的一点:
反序列化时类对象是不会调用该类构造方法的!!!!!
因为反序列化时是使用的 sun.reflect.ReflectionFactory.newConstructorForSerialization 来创建了一个反序列化专用的 Constructor, 接着使用这个 Constructor 来绕过构造方法创建类实例

当然还有另外一种绕过 Constructor 构造方法创建类实例可以查看前面的章节
Java速记->第二章-Java 安全基础->3. Java sun.misc.Unsafe的妙用进行学习查看

0x02 重点的类方法

  1. java.io.ObjectOutputStream 类最核心的方法就是 writeObject()方法,即序列化类对象
  2. java.io.ObjectInputStream 类最核心的方法就是 readObject()方法,即反序列化类对象
  3. 因此只需借助 ObjectOutputStream ObjectInputStream 类,即可实现类的序列化和反序列化功能
  4. 而其中:
  5. java.io.ObjectOutputStream 类,有几个常用方法分别是
  6. writeObject() # 自定义序列化类对象,类被序列化时调用
  7. writeReplace() # 写入替换对象时触发,类被序列化时调用
  8. 注意: writeObject() 方法是漏洞造成的重灾区之一, 因此需要认真学习并理解该方法
  9. java.io.ObjectInputStream 类,有几个常用方法分别是
  10. readObject() # 自定义反序列化类对象,反序列化时调用
  11. readResolve() # 读取对象时触发
  12. readObjectNoData() # 当对象序列化的版本和反序列化时的class版本不同时触发,注意这里指的是序列化与反序列化的版本,不是java版本
  13. readUnshared() # 此方法与readObject()相同,不同之处在于它阻止对readObject和readUnshared的后续调用返回对通过此调用获得的反序列化实例的额外引用
  14. 注意: readObject() 方法是漏洞造成的重灾区之一, 因此需要认真学习并理解该方法

0x03 java.io.Serializable 接口作用

java.io.Serializable 如果跟进去你就会发现,这是一个空接口,开发者无需实现里面的任何方法
代码如下:

  1. public interface Serializable {
  2. }

继承并实现 java.io.Serializable 这个接口的作用就是表明这个类可序列化
并且会通过这个类的方法与变量计算出来一个 serialVersionUID 常量

这个serialVersionUID是用来验证版本是否一致的
在进行反序列化时, JVM会把传进来的字节流中的 serialVersionUID 与本地相应实体类的serialVersionUID进行比较
如果有变化serialVersionUID就会不同, 导致InvalidClassException异常, 否则就进行反序列化

注意: serialVersionUID 是可以自己创建的,但是一般都不会自己去创建
创建 serialVersionUID 的例子例如:

  1. # 格式
  2. public class Person implements Serializable {
  3. # 喜欢多长自己写
  4. # 注意: 这个类型必须为long
  5. private static final long serialVersionUID = xL;
  6. }
  7. # 实例
  8. public class Person implements Serializable {
  9. private static final long serialVersionUID = 2239109261752820180L;
  10. }

0x04 例子

以下的例子主要是让我们知道这些方法在什么时候会被调用,心里有个大概的数,这样子方便理解后面的关于反序列化链子的文章

0x04.1 java.io.ObjectOutputStream 类

0x04.1.1 writeObject() - 重点方法

  1. // 第一步创建 SerializeTest1 类
  2. package java反序列化测试;
  3. import java.io.IOException;
  4. import java.io.ObjectOutputStream;
  5. import java.io.Serializable;
  6. public class SerializeTest1 implements Serializable {
  7. /**
  8. * 自定义序列化类对象
  9. * 调用: 类被序列化时调用
  10. * 注意: writeReplace() 与 writeObject() 同时存在时,执行 writeReplace()
  11. *
  12. * @param oos 序列化输出流对象
  13. * @throws IOException IO异常
  14. */
  15. private void writeObject(ObjectOutputStream oos) throws IOException {
  16. System.out.println("writeObject...");
  17. // ObjectOutputStream 默认反序列化方法
  18. oos.defaultWriteObject();
  19. }
  20. }
  1. // 第二步-序列化 SerializeTest1 类
  2. package java反序列化测试;
  3. import java.io.FileOutputStream;
  4. import java.io.IOException;
  5. import java.io.ObjectOutputStream;
  6. public class SerializeDemo1 {
  7. public static void main(String[] args) {
  8. // SerializeTest1 序列化
  9. SerializeTest1 e = new SerializeTest1();
  10. try {
  11. FileOutputStream fileOut = new FileOutputStream("./src/main/java/java反序列化测试/SerializeTest1.ser");
  12. ObjectOutputStream out = new ObjectOutputStream(fileOut);
  13. out.writeObject(e);
  14. out.close();
  15. fileOut.close();
  16. } catch (IOException i) {
  17. i.printStackTrace();
  18. }
  19. }
  20. }
  21. // 运行结果:
  22. // 输出: writeObject...
  23. // 并且会在在 /src/main/java/java反序列化测试/ 生成一个 SerializeTest1.ser 文件

0x04.1.2 writeReplace()

  1. // 第一步创建 SerializeTest2 类
  2. package java反序列化测试;
  3. import java.io.Serializable;
  4. import java.util.ArrayList;
  5. public class SerializeTest2 implements Serializable {
  6. /**
  7. * 写入替换对象时触发
  8. * 调用: 类被序列化时调用
  9. * 注意: writeReplace() 与 writeObject() 同时存在时,执行 writeReplace()
  10. * 启动这个以后获取的内容只有 list
  11. *
  12. * @return 替换后的对象
  13. */
  14. private Object writeReplace() {
  15. System.out.println("writeReplace....");
  16. ArrayList<Object> list = new ArrayList<>();
  17. list.add("test1");
  18. list.add("test2");
  19. return list;
  20. }
  21. }
  1. // 第二步-序列化 SerializeTest2 类
  2. // 并且反序列化获取 writeReplace() 方法,返回的数据
  3. package java反序列化测试;
  4. import java.io.*;
  5. public class SerializeDemo2 {
  6. public static void main(String[] args) {
  7. // SerializeTest2 序列化
  8. // 返回结果: writeReplace....
  9. try {
  10. SerializeTest2 e = new SerializeTest2();
  11. FileOutputStream fileOut = new FileOutputStream("./src/main/java/java反序列化测试/SerializeTest2.ser");
  12. ObjectOutputStream out = new ObjectOutputStream(fileOut);
  13. out.writeObject(e);
  14. out.close();
  15. fileOut.close();
  16. } catch (IOException i) {
  17. i.printStackTrace();
  18. }
  19. // SerializeTest2 反序列化读取 writeReplace() 返回的数据
  20. // 返回结果: [test1, test2]
  21. try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("./src/main/java/java反序列化测试/SerializeTest2.ser"))) {
  22. System.out.println((ois.readObject()).toString());
  23. } catch (FileNotFoundException er) {
  24. er.printStackTrace();
  25. } catch (IOException er) {
  26. er.printStackTrace();
  27. } catch (ClassNotFoundException er) {
  28. er.printStackTrace();
  29. }
  30. }
  31. }
  32. // 运行结果:
  33. // 1. SerializeTest2 序列化
  34. // 输出: writeReplace....
  35. // 并且会在在 /src/main/java/java反序列化测试/ 生成一个 SerializeTest2.ser 文件
  36. //
  37. // 2. SerializeTest2 反序列化读取 writeReplace() 返回的数据
  38. // 输出: [test1, test2]

0x04.2 java.io.ObjectOutputStream 类

0x04.2.1 readObject() - 重点方法

  1. // 第一步创建 DeserializationTest1 类
  2. package java反序列化测试;
  3. import java.io.IOException;
  4. import java.io.ObjectInputStream;
  5. import java.io.Serializable;
  6. public class DeserializationTest1 implements Serializable {
  7. /**
  8. * 自定义反序列化类对象
  9. * 调用: 反序列化时调用
  10. *
  11. * @param ois 反序列化输入流对象
  12. * @throws IOException IO异常
  13. * @throws ClassNotFoundException 类未找到异常
  14. */
  15. private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
  16. System.out.println("readObject...");
  17. // 调用ObjectInputStream默认反序列化方法
  18. ois.defaultReadObject();
  19. }
  20. }
  1. // 第二步-序列化 DeserializationTest1 类
  2. // 并且反序列化 DeserializationTest1 类
  3. package java反序列化测试;
  4. import java.io.*;
  5. public class DeserializationDemo1 {
  6. public static void main(String[] args) {
  7. // 序列化
  8. try {
  9. DeserializationTest1 e = new DeserializationTest1();
  10. FileOutputStream fileOut = new FileOutputStream("./src/main/java/java反序列化测试/DeserializationTest1.ser");
  11. ObjectOutputStream out = new ObjectOutputStream(fileOut);
  12. out.writeObject(e);
  13. out.close();
  14. fileOut.close();
  15. } catch (IOException i) {
  16. i.printStackTrace();
  17. }
  18. // 反序列化 DeserializationTest1
  19. try {
  20. FileInputStream fileIn = new FileInputStream("./src/main/java/java反序列化测试/DeserializationTest1.ser");
  21. ObjectInputStream in = new ObjectInputStream(fileIn);
  22. in.readObject();
  23. in.close();
  24. fileIn.close();
  25. } catch (IOException i) {
  26. i.printStackTrace();
  27. return;
  28. } catch (ClassNotFoundException c) {
  29. System.out.println("DeserializationTest1 class not found");
  30. c.printStackTrace();
  31. return;
  32. }
  33. }
  34. }
  35. // 运行结果:
  36. // 1. DeserializationTest1 序列化
  37. // 并且会在在 /src/main/java/java反序列化测试/ 生成一个 DeserializationTest1.ser 文件
  38. //
  39. // 2. 反序列化 DeserializationTest1 并且自动调用了 readObject()
  40. // 输出: readObject...

0x04.2.2 readResolve()

  1. // 第一步创建 DeserializationTest2 类
  2. package java反序列化测试;
  3. import java.io.Serializable;
  4. public class DeserializationTest2 implements Serializable {
  5. /**
  6. * 读取对象时触发
  7. * 有了这个以后 getTestName() 与 getTestValue() 会访问不到
  8. *
  9. * @return 读取后的对象
  10. */
  11. protected Object readResolve() {
  12. System.out.println("readResolve....");
  13. return null;
  14. }
  15. }
  1. // 第二步-序列化 DeserializationTest2 类
  2. // 并且反序列化 DeserializationTest2 类
  3. package java反序列化测试;
  4. import java.io.*;
  5. public class DeserializationDemo2 {
  6. public static void main(String[] args) {
  7. // 序列化
  8. try {
  9. DeserializationTest2 e = new DeserializationTest2();
  10. FileOutputStream fileOut = new FileOutputStream("./src/main/java/java反序列化测试/DeserializationTest2.ser");
  11. ObjectOutputStream out = new ObjectOutputStream(fileOut);
  12. out.writeObject(e);
  13. out.close();
  14. fileOut.close();
  15. } catch (IOException i) {
  16. i.printStackTrace();
  17. }
  18. // 反序列化 DeserializationTest1
  19. try {
  20. FileInputStream fileIn = new FileInputStream("./src/main/java/java反序列化测试/DeserializationTest2.ser");
  21. ObjectInputStream in = new ObjectInputStream(fileIn);
  22. in.readObject();
  23. in.close();
  24. fileIn.close();
  25. } catch (IOException i) {
  26. i.printStackTrace();
  27. return;
  28. } catch (ClassNotFoundException c) {
  29. System.out.println("DeserializationTest2 class not found");
  30. c.printStackTrace();
  31. return;
  32. }
  33. }
  34. }
  35. // 运行结果:
  36. // 1. DeserializationTest2 序列化
  37. // 并且会在在 /src/main/java/java反序列化测试/ 生成一个 DeserializationTest2.ser 文件
  38. //
  39. // 2. 反序列化 DeserializationTest2 并且自动调用了 readResolve()
  40. // 输出: readResolve....

0x04.2.3 readObjectNoData()

这个方法,说实在,我在看反序列化链子的时候就没见过有怎么使用,并且触发的方式也是奇奇怪怪,所以这个方法很值得我们写个例子,搞清楚到底要怎么样才会触发,然后知道有这么一个东西即可

注意: 它的触发方法是,当对象序列化的版本和反序列化时的class版本不同时触发,注意这里指的是序列化与反序列化的版本,不是java版本

注意2: readObjectNoData() 方法是最先执行的

  1. // 第一步创建 DeserializationTest3 类
  2. package java反序列化测试;
  3. import java.io.Serializable;
  4. public class DeserializationTest3 implements Serializable {
  5. }
  1. // 第二步创建 ReadObjectNoDataTest 类
  2. package java反序列化测试;
  3. import java.io.Serializable;
  4. public class ReadObjectNoDataTest implements Serializable {
  5. /**
  6. * 当对象序列化的版本和反序列化时的class版本不同时触发,注意这里指的是序列化与反序列化的版本,不是java版本
  7. */
  8. private void readObjectNoData() {
  9. System.out.println("readObjectNoData...");
  10. }
  11. }
  1. // 第三步-序列化 DeserializationTest3 类
  2. package java反序列化测试;
  3. import java.io.FileOutputStream;
  4. import java.io.IOException;
  5. import java.io.ObjectOutputStream;
  6. public class SerializeDemo3 {
  7. public static void main(String[] args) {
  8. // DeserializationTest3 序列化
  9. try {
  10. DeserializationTest3 e = new DeserializationTest3();
  11. FileOutputStream fileOut = new FileOutputStream("./src/main/java/java反序列化测试/DeserializationTest3.ser");
  12. ObjectOutputStream out = new ObjectOutputStream(fileOut);
  13. out.writeObject(e);
  14. out.close();
  15. fileOut.close();
  16. } catch (IOException i) {
  17. i.printStackTrace();
  18. }
  19. }
  20. }
  21. // 运行结果:
  22. // DeserializationTest3 序列化
  23. // 并且会在在 /src/main/java/java反序列化测试/ 生成一个 DeserializationTest3.ser 文件
  1. // 第四步-修改 DeserializationTest3 类如下
  2. package java反序列化测试;
  3. import java.io.Serializable;
  4. public class DeserializationTest3 extends ReadObjectNoDataTest implements Serializable {
  5. }
  1. // 第五步-反序列化 DeserializationTest3 类
  2. package java反序列化测试;
  3. import java.io.FileInputStream;
  4. import java.io.IOException;
  5. import java.io.ObjectInputStream;
  6. public class DeserializationDemo3 {
  7. public static void main(String[] args) {
  8. // 反序列化 DeserializationTest3
  9. try {
  10. FileInputStream fileIn = new FileInputStream("./src/main/java/java反序列化测试/DeserializationTest3.ser");
  11. ObjectInputStream in = new ObjectInputStream(fileIn);
  12. in.readObject();
  13. in.close();
  14. fileIn.close();
  15. } catch (IOException i) {
  16. i.printStackTrace();
  17. return;
  18. } catch (ClassNotFoundException c) {
  19. System.out.println("DeserializationTest3 class not found");
  20. c.printStackTrace();
  21. return;
  22. }
  23. }
  24. }
  25. // 运行结果:
  26. // 输出: readObjectNoData...

0x04.2.4 readUnshared()

  1. // 创建 ReadUnsharedDemo 类
  2. package java反序列化测试;
  3. import java.io.FileInputStream;
  4. import java.io.FileOutputStream;
  5. import java.io.ObjectInputStream;
  6. import java.io.ObjectOutputStream;
  7. public class ReadUnsharedDemo {
  8. public static void main(String[] args) {
  9. String s = "Hello World";
  10. try {
  11. // 序列化文件
  12. FileOutputStream fileOut = new FileOutputStream("./src/main/java/java反序列化测试/test.txt");
  13. ObjectOutputStream out = new ObjectOutputStream(fileOut);
  14. out.writeUnshared(s);
  15. out.flush();
  16. // 反序列化文件
  17. ObjectInputStream ois = new ObjectInputStream(new FileInputStream("./src/main/java/java反序列化测试/test.txt"));
  18. // 读取和打印非共享对象
  19. System.out.println("" + ois.readUnshared());
  20. } catch (Exception ex) {
  21. ex.printStackTrace();
  22. }
  23. }
  24. }
  25. // 运行结果:
  26. // 输出: Hello World