单例模式序列化攻击以及解决方案
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
package top.fjy8018.designpattern.pattern.creational.singleton;
|
package top.fjy8018.designpattern.pattern.creational.singleton;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 单例模式:饿汉式(类加载时初始化)
|
* 单例模式:饿汉式(类加载时初始化)
|
||||||
* 优点:写法简单,类加载时初始化,线程安全
|
* 优点:写法简单,类加载时初始化,线程安全
|
||||||
@@ -8,7 +10,7 @@ package top.fjy8018.designpattern.pattern.creational.singleton;
|
|||||||
* @author F嘉阳
|
* @author F嘉阳
|
||||||
* @date 2018-09-24 15:38
|
* @date 2018-09-24 15:38
|
||||||
*/
|
*/
|
||||||
public class HungrySingleton {
|
public class HungrySingleton implements Serializable {
|
||||||
/**
|
/**
|
||||||
* 类加载时初始化
|
* 类加载时初始化
|
||||||
* 声明final(可选),只有在类加载时初始化才能声明为final,故懒汉式不能声明为final
|
* 声明final(可选),只有在类加载时初始化才能声明为final,故懒汉式不能声明为final
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user