类加载单例模式反射攻击和防御
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user