类加载单例模式反射攻击和防御

This commit is contained in:
2018-09-24 16:53:43 +08:00
parent b507f4e478
commit 5251def1b0
5 changed files with 239 additions and 2 deletions

View File

@@ -0,0 +1,47 @@
package top.fjy8018.designpattern.pattern.creational.singleton;
import lombok.extern.slf4j.Slf4j;
import java.io.Serializable;
/**
* 单例模式:饿汉式
* 反射攻击改进
* 对于在类加载时进行初始化的单例,可以用该方法解决反射攻击问题
*
* @author F嘉阳
* @date 2018-09-24 15:38
*/
@Slf4j
public class HungrySingletonReflectImprove implements Serializable {
private static final HungrySingletonReflectImprove HUNGRYSINGLETON;
static {
log.debug(HungrySingletonReflectImprove.class.getSimpleName() + "静态块实例化");
HUNGRYSINGLETON = new HungrySingletonReflectImprove();
}
private HungrySingletonReflectImprove() {
if (HUNGRYSINGLETON != null) {
throw new RuntimeException("单例模式禁止反射调用");
}
log.debug(HungrySingletonReflectImprove.class.getSimpleName() + "构造器实例化");
}
public static HungrySingletonReflectImprove getInstance() {
return HUNGRYSINGLETON;
}
/**
* 解决序列化攻击问题
*
* @return
*/
private Object readResolve() {
log.debug("序列化获取对象");
return HUNGRYSINGLETON;
}
}

View File

@@ -27,12 +27,12 @@ public class HungrySingletonSerializableImprove implements Serializable {
private static final HungrySingletonSerializableImprove HUNGRYSINGLETON; private static final HungrySingletonSerializableImprove HUNGRYSINGLETON;
static { static {
log.debug(HungrySingletonSerializableImprove.class.getSimpleName() + "实例化"); log.debug(HungrySingletonSerializableImprove.class.getSimpleName() + "静态块实例化");
HUNGRYSINGLETON = new HungrySingletonSerializableImprove(); HUNGRYSINGLETON = new HungrySingletonSerializableImprove();
} }
private HungrySingletonSerializableImprove() { private HungrySingletonSerializableImprove() {
log.debug(HungrySingletonSerializableImprove.class.getSimpleName() + "实例化"); log.debug(HungrySingletonSerializableImprove.class.getSimpleName() + "构造器实例化");
} }
public static HungrySingletonSerializableImprove getInstance() { public static HungrySingletonSerializableImprove getInstance() {

View File

@@ -16,6 +16,7 @@ public class StaticInnerClassSingleton {
* 私有构造器是单例必须的 * 私有构造器是单例必须的
*/ */
private StaticInnerClassSingleton() { private StaticInnerClassSingleton() {
log.debug(StaticInnerClassSingleton.class.getSimpleName() + "构造器实例化");
} }
/** /**
@@ -24,6 +25,7 @@ public class StaticInnerClassSingleton {
* @return * @return
*/ */
public static StaticInnerClassSingleton getInstance() { public static StaticInnerClassSingleton getInstance() {
log.debug(StaticInnerClassSingleton.class.getSimpleName() + "内部类实例化");
return InnerClass.staticInnerClassSingleton; return InnerClass.staticInnerClassSingleton;
} }

View File

@@ -0,0 +1,70 @@
package top.fjy8018.designpattern.pattern.creational.singleton;
import lombok.extern.slf4j.Slf4j;
/**
* 懒汉单例:静态内部类实现(线程安全)
* 反射攻击改进
*
* @author F嘉阳
* @date 2018-09-24 13:11
*/
@Slf4j
public class StaticInnerClassSingletonReflectImprove {
/**
* 私有构造器是单例必须的
*/
private StaticInnerClassSingletonReflectImprove() {
if (InnerClass.staticInnerClassSingleton != null) {
throw new RuntimeException("单例模式禁止反射调用");
}
log.debug(StaticInnerClassSingletonReflectImprove.class.getSimpleName() + "构造器实例化");
}
/**
* 开发单例入口
*
* @return
*/
public static StaticInnerClassSingletonReflectImprove getInstance() {
log.debug(StaticInnerClassSingletonReflectImprove.class.getSimpleName() + "内部类实例化");
return InnerClass.staticInnerClassSingleton;
}
/**
* 静态内部类
* <p>
* 原理:
* <strong>JVM在类初始化时会加锁使用初始化锁可以同步多个线程对一个类的初始化确保了线程安全</strong>
* 类初始化时机
* 一. 主动引用
* 虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了有且只有下列五种情况必须对类进行初始化(加载、验证、准备都会随之发生):
* 1. 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则必须先触发其初始化。
* 最常见的生成这 4 条指令的场景是:
* (1) 使用 new 关键字实例化对象的时候;
* (2) 读取或赋值一个类的静态字段(被 final 修饰、已在编译期把结果放入常量池的静态字段除外)的时候;
* (3) 以及调用一个类的静态方法的时候。
* (4) 使用一个类的非常量静态成员
* (5) 若一个类是顶级类,且该类有嵌套的断言语句(少见)
* <p>
* 2. 使用 {@link java.lang.reflect} 包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。
* 3. 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
* 4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类;
* 5. 当使用 JDK 1.7 的动态语言支持时,如果一个 {@link java.lang.invoke.MethodHandle}
* 实例最后的解析结果为 REF_getStatic, REF_putStatic, REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化;
* <p>
* 二. 被动引用
* 以上 5 种场景中的行为称为对一个类进行主动引用。除此之外,所有引用类的方式都不会触发初始化,称为被动引用。被动引用的常见例子包括:
* <p>
* 1. 通过子类引用父类的静态字段,不会导致子类初始化。
* System.out.println(SubClass.value); // value 字段在 SuperClass 中定义
* 2. 通过数组定义来引用类,不会触发此类的初始化。该过程会对数组类进行初始化,数组类是一个由虚拟机自动生成的、直接继承自 {@link Object} 的子类,其中包含了数组的属性和方法。
* SuperClass[] sca = new SuperClass[10];
* 3. 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。
* System.out.println(ConstClass.HELLOWORLD);
*/
private static class InnerClass {
private static StaticInnerClassSingletonReflectImprove staticInnerClassSingleton = new StaticInnerClassSingletonReflectImprove();
}
}

View File

@@ -0,0 +1,118 @@
package top.fjy8018.designpattern.pattern.creational.singleton;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import static org.junit.jupiter.api.Assertions.*;
/**
* 单例模式反射攻击
*/
@Slf4j
class SingletonReflectAttack {
/**
* 修复前饿汉式攻击
*
* @throws NoSuchMethodException
* @throws IllegalAccessException
* @throws InvocationTargetException
* @throws InstantiationException
*/
@Test
void HungrySingletongetInstanceBefore() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class clazz = HungrySingletonSerializableImprove.class;
Constructor constructor = clazz.getDeclaredConstructor();
// 打开访问权限
constructor.setAccessible(true);
// 通过反射获取实例
HungrySingletonSerializableImprove reflectInstance = (HungrySingletonSerializableImprove) constructor.newInstance();
// 通过正常方式获取
HungrySingletonSerializableImprove instance = HungrySingletonSerializableImprove.getInstance();
// 判断是否同一个对象
log.info("反射方式:" + reflectInstance);
log.info("正常方式:" + instance);
// 此处结果为false说明是两个不同的对象
log.info(String.valueOf(reflectInstance == instance));
}
/**
* 修复后饿汉式攻击
*
* @throws NoSuchMethodException
* @throws IllegalAccessException
* @throws InvocationTargetException
* @throws InstantiationException
*/
@Test
void HungrySingletongetInstanceAfter() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class clazz = HungrySingletonReflectImprove.class;
Constructor constructor = clazz.getDeclaredConstructor();
// 打开访问权限
constructor.setAccessible(true);
// 通过反射获取实例,此处抛出异常
HungrySingletonReflectImprove reflectInstance = (HungrySingletonReflectImprove) constructor.newInstance();
// 通过正常方式获取
HungrySingletonReflectImprove instance = HungrySingletonReflectImprove.getInstance();
// 判断是否同一个对象
log.info("反射方式:" + reflectInstance);
log.info("正常方式:" + instance);
log.info(String.valueOf(reflectInstance == instance));
}
/**
* 修复前懒汉式攻击
*
* @throws NoSuchMethodException
* @throws IllegalAccessException
* @throws InvocationTargetException
* @throws InstantiationException
*/
@Test
void StaticInnerClassSingletongetInstanceBefore() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class clazz = StaticInnerClassSingleton.class;
Constructor constructor = clazz.getDeclaredConstructor();
// 打开访问权限
constructor.setAccessible(true);
// 通过反射获取实例
StaticInnerClassSingleton reflectInstance = (StaticInnerClassSingleton) constructor.newInstance();
// 通过正常方式获取
StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();
// 判断是否同一个对象
log.info("反射方式:" + reflectInstance);
log.info("正常方式:" + instance);
// 此处结果为false说明是两个不同的对象
log.info(String.valueOf(reflectInstance == instance));
}
/**
* 修复后懒汉式攻击
*
* @throws NoSuchMethodException
* @throws IllegalAccessException
* @throws InvocationTargetException
* @throws InstantiationException
*/
@Test
void StaticInnerClassSingletongetInstanceAfter() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class clazz = StaticInnerClassSingletonReflectImprove.class;
Constructor constructor = clazz.getDeclaredConstructor();
// 打开访问权限
constructor.setAccessible(true);
// 通过反射获取实例
StaticInnerClassSingletonReflectImprove reflectInstance = (StaticInnerClassSingletonReflectImprove) constructor.newInstance();
// 通过正常方式获取
StaticInnerClassSingletonReflectImprove instance = StaticInnerClassSingletonReflectImprove.getInstance();
// 判断是否同一个对象
log.info("反射方式:" + reflectInstance);
log.info("正常方式:" + instance);
log.info(String.valueOf(reflectInstance == instance));
}
}