From 8aacfe3ed983f5653e4ddf0196e82a83aba38def Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=E5=98=89=E9=98=B3?= Date: Mon, 24 Sep 2018 16:26:19 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8D=95=E4=BE=8B=E6=A8=A1=E5=BC=8F=E5=BA=8F?= =?UTF-8?q?=E5=88=97=E5=8C=96=E6=94=BB=E5=87=BB=E4=BB=A5=E5=8F=8A=E8=A7=A3?= =?UTF-8?q?=E5=86=B3=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../creational/singleton/HungrySingleton.java | 4 +- .../HungrySingletonSerializableImprove.java | 47 ++++++++++++ .../SingletonSerializableAttackTest.java | 76 +++++++++++++++++++ 3 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 src/main/java/top/fjy8018/designpattern/pattern/creational/singleton/HungrySingletonSerializableImprove.java create mode 100644 src/test/java/top/fjy8018/designpattern/pattern/creational/singleton/SingletonSerializableAttackTest.java diff --git a/src/main/java/top/fjy8018/designpattern/pattern/creational/singleton/HungrySingleton.java b/src/main/java/top/fjy8018/designpattern/pattern/creational/singleton/HungrySingleton.java index 4c3f5f0..dcd62fc 100644 --- a/src/main/java/top/fjy8018/designpattern/pattern/creational/singleton/HungrySingleton.java +++ b/src/main/java/top/fjy8018/designpattern/pattern/creational/singleton/HungrySingleton.java @@ -1,5 +1,7 @@ package top.fjy8018.designpattern.pattern.creational.singleton; +import java.io.Serializable; + /** * 单例模式:饿汉式(类加载时初始化) * 优点:写法简单,类加载时初始化,线程安全 @@ -8,7 +10,7 @@ package top.fjy8018.designpattern.pattern.creational.singleton; * @author F嘉阳 * @date 2018-09-24 15:38 */ -public class HungrySingleton { +public class HungrySingleton implements Serializable { /** * 类加载时初始化 * 声明final(可选),只有在类加载时初始化才能声明为final,故懒汉式不能声明为final diff --git a/src/main/java/top/fjy8018/designpattern/pattern/creational/singleton/HungrySingletonSerializableImprove.java b/src/main/java/top/fjy8018/designpattern/pattern/creational/singleton/HungrySingletonSerializableImprove.java new file mode 100644 index 0000000..307bce2 --- /dev/null +++ b/src/main/java/top/fjy8018/designpattern/pattern/creational/singleton/HungrySingletonSerializableImprove.java @@ -0,0 +1,47 @@ +package top.fjy8018.designpattern.pattern.creational.singleton; + +import java.io.ObjectInputStream; +import java.io.ObjectStreamClass; +import java.io.Serializable; + +/** + * 单例模式:饿汉式 + * 序列化攻击改进 + * 序列化导致单例失效原因:{@link ObjectInputStream#readObject0(boolean)} 1568行中读取对象 + * {@link ObjectInputStream#readOrdinaryObject(boolean)} 2048行检验对象是否实例化 + * {@link ObjectStreamClass#isInstantiable()} 该方法的注释说明了,若实现了{@link Serializable}且能在运行时实例化则返回true + * 返回true后序列化过程通过反射机制实例化该类 + *

+ * 改进方法:在{@link ObjectInputStream#readOrdinaryObject(boolean)} 2071行会检测内部方法 + * 在{@link ObjectStreamClass#hasReadResolveMethod()} 中,判断是否存在名为readResolve的方法,方法名定义在522行 + * 若有该方法,则用该方法返回的对象写入 + * + * @author F嘉阳 + * @date 2018-09-24 15:38 + */ +public class HungrySingletonSerializableImprove implements Serializable { + + private static final HungrySingletonSerializableImprove HUNGRYSINGLETON; + + static { + HUNGRYSINGLETON = new HungrySingletonSerializableImprove(); + } + + private HungrySingletonSerializableImprove() { + } + + public static HungrySingletonSerializableImprove getInstance() { + return HUNGRYSINGLETON; + } + + /** + * 解决序列化攻击问题 + * + * @return + */ + private Object readResolve() { + return HUNGRYSINGLETON; + } + + +} diff --git a/src/test/java/top/fjy8018/designpattern/pattern/creational/singleton/SingletonSerializableAttackTest.java b/src/test/java/top/fjy8018/designpattern/pattern/creational/singleton/SingletonSerializableAttackTest.java new file mode 100644 index 0000000..2bf8a60 --- /dev/null +++ b/src/test/java/top/fjy8018/designpattern/pattern/creational/singleton/SingletonSerializableAttackTest.java @@ -0,0 +1,76 @@ +package top.fjy8018.designpattern.pattern.creational.singleton; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.*; + +/** + * 单例模式序列化攻击 + */ +@Slf4j +class SingletonSerializableAttackTest { + + private static final String FILE_NAME = "tmp/singleton_obj.dat"; + + @BeforeEach + void init() { + File dir = new File("tmp"); + boolean mkdir = false; + if (!dir.exists()) { + mkdir = dir.mkdir(); + if (mkdir) { + log.debug("文件夹新建成功"); + } else { + log.error("文件夹新建失败"); + } + } + + File file = new File(FILE_NAME); + file.deleteOnExit(); + } + + /** + * 通过序列化测试写入和读出后是否属于同一个对象 + * 根据单例模式原则,永远只有一个实例化的对象 + * + * @throws IOException + */ + @Test + void getInstanceBefore() throws IOException, ClassNotFoundException { + + HungrySingleton hungrySingleton = HungrySingleton.getInstance(); + ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_NAME)); + oos.writeObject(hungrySingleton); + + ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE_NAME)); + HungrySingleton newInstance = (HungrySingleton) ois.readObject(); + + log.info("写入前:" + hungrySingleton); + log.info("读出后:" + newInstance); + // 此处结果为false,说明是两个不同的对象 + log.info(String.valueOf(newInstance == hungrySingleton)); + + oos.close(); + ois.close(); + } + + @Test + void getInstanceAfter() throws IOException, ClassNotFoundException { + HungrySingletonSerializableImprove before = HungrySingletonSerializableImprove.getInstance(); + ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_NAME)); + oos.writeObject(before); + + ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE_NAME)); + HungrySingletonSerializableImprove after = (HungrySingletonSerializableImprove) ois.readObject(); + + log.info("写入前:" + before); + log.info("读出后:" + after); + // 此处结果为true,说明是同一个对象 + log.info(String.valueOf(before == after)); + + oos.close(); + ois.close(); + } +} \ No newline at end of file