枚举类实现单例,序列化攻击测试、反射攻击测试、反编译分析
This commit is contained in:
53
jad/EnumInstance.jad
Normal file
53
jad/EnumInstance.jad
Normal file
@@ -0,0 +1,53 @@
|
||||
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
|
||||
// Jad home page: http://www.kpdus.com/jad.html
|
||||
// Decompiler options: packimports(3)
|
||||
// Source File Name: EnumInstance.java
|
||||
|
||||
package top.fjy8018.designpattern.pattern.creational.singleton;
|
||||
|
||||
|
||||
public final class EnumInstance extends Enum
|
||||
{
|
||||
|
||||
public static EnumInstance[] values()
|
||||
{
|
||||
return (EnumInstance[])$VALUES.clone();
|
||||
}
|
||||
|
||||
public static EnumInstance valueOf(String name)
|
||||
{
|
||||
return (EnumInstance)Enum.valueOf(top/fjy8018/designpattern/pattern/creational/singleton/EnumInstance, name);
|
||||
}
|
||||
|
||||
private EnumInstance(String s, int i)
|
||||
{
|
||||
super(s, i);
|
||||
}
|
||||
|
||||
public Object getData()
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(Object data)
|
||||
{
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public Object getInstance()
|
||||
{
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public static final EnumInstance INSTANCE;
|
||||
private Object data;
|
||||
private static final EnumInstance $VALUES[];
|
||||
|
||||
static
|
||||
{
|
||||
INSTANCE = new EnumInstance("INSTANCE", 0);
|
||||
$VALUES = (new EnumInstance[] {
|
||||
INSTANCE
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package top.fjy8018.designpattern.pattern.creational.singleton;
|
||||
|
||||
/**
|
||||
* 枚举单例模式(effective Java推荐)
|
||||
* 枚举类天生可序列化,有效防止序列化攻击
|
||||
* <p>
|
||||
* 通过反编译可知:
|
||||
* 1. 枚举类的通过final修饰,不能被继承
|
||||
* 2. 其构造器私有,不能被调用,如果通过反射强制调用,在newInstance方法中会抛出异常
|
||||
* 3. 枚举变量声明为final和static
|
||||
* 4. 枚举变量通过静态块实例化(类似饿汉式),所以线程安全
|
||||
*
|
||||
* @author F嘉阳
|
||||
* @date 2018-09-24 20:30
|
||||
*/
|
||||
public enum EnumInstance {
|
||||
/**
|
||||
* 单例枚举
|
||||
*/
|
||||
INSTANCE;
|
||||
|
||||
/**
|
||||
* 目标实例化对象
|
||||
*/
|
||||
private Object data;
|
||||
|
||||
public Object getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(Object data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public Object getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,37 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
@Slf4j
|
||||
class SingletonReflectAttack {
|
||||
|
||||
/**
|
||||
* 枚举单例反射攻击
|
||||
* 枚举类只有一个构造器 {@link Enum#Enum(String, int)}
|
||||
* 由于{@link Constructor#newInstance(Object...)} 416行指明枚举类不允许反射实例化,强制实例化会抛{@link IllegalArgumentException}
|
||||
* 从底层保证了枚举类无法被反射攻击
|
||||
*
|
||||
* @throws NoSuchMethodException
|
||||
* @throws IllegalAccessException
|
||||
* @throws InvocationTargetException
|
||||
* @throws InstantiationException
|
||||
*/
|
||||
@Test
|
||||
void enumInstanceGetInstance() throws Exception {
|
||||
Class clazz = EnumInstance.class;
|
||||
// 由于枚举类没有无参构造器,需要指定参数
|
||||
Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class);
|
||||
|
||||
// 打开访问权限
|
||||
constructor.setAccessible(true);
|
||||
// 通过反射获取实例
|
||||
EnumInstance reflectInstance = (EnumInstance) constructor.newInstance("fjy", 123);
|
||||
// 通过正常方式获取
|
||||
EnumInstance instance = EnumInstance.INSTANCE;
|
||||
|
||||
// 判断是否同一个对象
|
||||
log.info("反射方式:" + reflectInstance.getData());
|
||||
log.info("正常方式:" + instance.getData());
|
||||
// 此处结果为false,说明是两个不同的对象
|
||||
log.info(String.valueOf(reflectInstance.getData() == instance.getData()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修复前懒汉式反射攻击
|
||||
* 由于反射攻击足够强大,对于非类加载时实例化的单例模式,是无法彻底防止反射攻击的
|
||||
@@ -96,7 +127,7 @@ class SingletonReflectAttack {
|
||||
|
||||
// 通过反射获取实例
|
||||
LazySingletonSynchronizedReflectImprove2 reflectInstance = (LazySingletonSynchronizedReflectImprove2) constructor.newInstance();
|
||||
|
||||
|
||||
// 判断是否同一个对象
|
||||
log.info("反射方式:" + reflectInstance);
|
||||
log.info("正常方式:" + instance);
|
||||
|
||||
@@ -31,6 +31,34 @@ class SingletonSerializableAttackTest {
|
||||
file.deleteOnExit();
|
||||
}
|
||||
|
||||
/**
|
||||
* 枚举类序列化攻击
|
||||
* 原因:{@link ObjectInputStream#readEnum(boolean)} 中对枚举的执行过程是2000 - 2007行
|
||||
* 通过枚举的类和名称来获取对象实例,因为枚举的名称在枚举中唯一,所以没有实例化的动作,保证了单例可靠性
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@Test
|
||||
void EnumInstanceGetInstance() throws Exception {
|
||||
|
||||
EnumInstance singleton = EnumInstance.INSTANCE;
|
||||
singleton.setData(new Object());
|
||||
|
||||
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_NAME));
|
||||
oos.writeObject(singleton);
|
||||
|
||||
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE_NAME));
|
||||
EnumInstance newInstance = (EnumInstance) ois.readObject();
|
||||
|
||||
log.info("写入前:" + singleton.getData());
|
||||
log.info("读出后:" + newInstance.getData());
|
||||
// 此处结果为false,说明是两个不同的对象
|
||||
log.info(String.valueOf(newInstance.getData() == singleton.getData()));
|
||||
|
||||
oos.close();
|
||||
ois.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过序列化测试写入和读出后是否属于同一个对象
|
||||
* 根据单例模式原则,永远只有一个实例化的对象
|
||||
|
||||
Reference in New Issue
Block a user