如何防止單例模式被 JAVA 反射攻擊
阿新 • • 發佈:2018-11-24
如何防止單例模式被 JAVA 反射攻擊
package sf.com.singleton; public class Demo { private static boolean flag = true; private Demo() { System.out.println("flag==" + flag); } private static class SingletonHolder{ private static final Demo INSTANCE = new Demo(); } public static Demo getInstance(){ return SingletonHolder.INSTANCE; } public void deSomethingElse(){ } } package sf.com.singleton; import java.lang.reflect.Constructor; public class DemoRelectAttack { public static void main(String[] args){ try { Class<Demo> classType = Demo.class; Constructor<Demo> constructor = classType.getDeclaredConstructor(null); //取消java的許可權控制檢查 constructor.setAccessible(true); //下面兩個都可以訪問私有構造器 Demo demo1 = (Demo) constructor.newInstance(); Demo demo2 = Demo.getInstance(); System.out.println(demo1 == demo2); } catch (Exception e) { e.printStackTrace(); } } }
執行結果:
可以看到,通過反射獲取建構函式,然後呼叫setAccessible(true)就可以呼叫私有的建構函式,所有e1和e2是兩個不同的物件。
如果要抵禦這種攻擊,可以修改構造器,讓它在被要求建立第二個例項的時候丟擲異常。
修改後的程式碼:
package sf.com.singleton; public class DemoModify { private static boolean flag = true; private DemoModify() { synchronized (DemoModify.class) { if (flag == false) { flag = !flag; }else{ throw new RuntimeException("單例模式被侵犯!"); } } } private static class SingletonHolder{ private static final DemoModify INSTANCE = new DemoModify(); } public static DemoModify getInstance(){ return SingletonHolder.INSTANCE; } public void deSomethingElse(){ } } package sf.com.singleton; import java.lang.reflect.Constructor; public class DemoRelectAttack { public static void main(String[] args){ try { Class<DemoModify> classType = DemoModify.class; Constructor<DemoModify> constructor = classType.getDeclaredConstructor(null); //取消java的許可權控制檢查 constructor.setAccessible(true); //下面兩個都可以訪問私有構造器 DemoModify demo1 = (DemoModify) constructor.newInstance(); DemoModify demo2 = DemoModify.getInstance(); System.out.println(demo1 == demo2); } catch (Exception e) { e.printStackTrace(); } } }
執行結果如下:
可以看到,成功的阻止了單例模式被破壞。
從JDK1.5開始,實現Singleton還有新的寫法,只需編寫一個包含單個元素的列舉型別。推薦寫法:
package sf.com.singleton; public enum SingletonClass { INSTANCE; public void test(){ System.out.println("The Test!"); } } package sf.com.singleton; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public class TestMain { public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { Class<SingletonClass> classType = SingletonClass.class; Constructor<SingletonClass> constructor =(Constructor<SingletonClass>) classType.getDeclaredConstructor(); constructor.setAccessible(true); constructor.newInstance(); } }
執行結果如下:
由此可見這種寫法也可以防止單例模式被“攻擊”。
2、第二種方法簡單直接
private Singleton() {
if(singleton!=null){
throw new RuntimeException();
}
}
public class Demo {
public static void main(String[] args) throws Exception {
Singleton singletonOne = Singleton.getSingleton();
Singleton singletonTwo = Singleton.getSingleton();
System.out.println("One和Two是否是同一個例項? " + (singletonOne == singletonTwo));
//利用反射來破壞單例模式
Class clazz = Class.forName("com.jiagouedu.Singleton");
Constructor constructor = clazz.getDeclaredConstructor(null);
constructor.setAccessible(true);
Singleton singletonThree = (Singleton) constructor.newInstance(null);
System.out.println("One和three是否是同一個例項? "+(singletonOne==singletonThree));
}
}
//雙重檢測鎖模式
class Singleton {
private Singleton() {
if(singleton!=null){
throw new RuntimeException();
}
}
static Singleton singleton;
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}