单例模式序列化攻击以及解决方案

This commit is contained in:
2018-09-24 16:26:19 +08:00
parent 41595084b6
commit 8aacfe3ed9
3 changed files with 126 additions and 1 deletions

View File

@@ -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

View File

@@ -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后序列化过程通过反射机制实例化该类
* <p>
* 改进方法:在{@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;
}
}

View File

@@ -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();
}
}