單例模式安全之反射攻擊
單例模式這裏就不談了,什麼是單例模式可參考七種Java單例模式詳解,這裏是關於單例模式安全方面的,當然了這裏說的安全不是線程安全。
什麼是反射攻擊呢
- 在Java中,由於反射的功能實在是太強了,通過動態訪問類並設置Access使得可以訪問對象的私有屬性方法等。
- 在單例模式中,我們使用private 修飾構造方法對外隱藏,防止外部new 對象,但是在反射的存在下,private的存在形同虛設,通過反射設置Access即可訪問構造方法,這時的單例就不是單例了。
反射攻擊重現
這裏使用volatile 雙重檢驗鎖實現線程安全的單例
package com.fine.reflect;
/**
* 單例
* volatile 雙重校驗
*
* @author finefine at: 2019-05-02 22:22
*/
public class Singleton {
private volatile static Singleton INSTANCE;
private Singleton() {
}
public static Singleton getInstance() {
if (INSTANCE==null){
//同步代碼塊
synchronized (Singleton.class){
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
反射攻擊實現
package com.fine.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* @author finefine at: 2019-05-02 22:26
*/
public class ReflectAttackTest {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
Class clazz = singleton.getClass();
try {
Constructor<Singleton> constructor = clazz.getDeclaredConstructors()[0];
//設置允許訪問私有的構造器
constructor.setAccessible(true);
Singleton singleton1 = constructor.newInstance();
if (singleton1 != null&&singleton1.getClass().equals(singleton.getClass())) {
System.out.println("通過反射構造除了對象");
return;
}
if (singleton == singleton1) {
System.out.println("是同一個對象");
} else {
System.out.println("是兩個不同的對象");
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
運行結果
debug 分析
可以看到singleton 和 singleton1 都是Singleton 的對象,一個hashcode是467一個是469。constructor=private com.fine.reflect.Singleton() 說明了的確是Singleton的私有構造方法。
通過反射,我們創造出了不止一個對象,因此該單例模式配破壞了,是不安全的,這就是反射攻擊。
預防反射攻擊
要避免通過反射來調用私有構造器這是行不通的,那麼該如何做呢,這裏有兩種做法。
- 當嘗試使用構造方法new 對象時,直接拋出異常
- 使用枚舉,枚舉類jvm底層保證了不可new。
第一種
單例寫法:
package com.fine.reflect.enhance;
/**
* 單例
* volatile 雙重校驗
*
* @author finefine at: 2019-05-02 22:22
*/
public class Singleton {
private volatile static Singleton INSTANCE;
private Singleton() {
//如果已存在,直接拋出異常,保證只會被new 一次
if (INSTANCE != null) {
throw new RuntimeException("對象已存在不可重複創建");
}
}
public static Singleton getInstance() {
if (INSTANCE==null){
//同步代碼塊
synchronized (Singleton.class){
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
測試代碼:
package com.fine.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import com.fine.reflect.enhance.Singleton;
/**
* @author finefine at: 2019-05-02 22:26
*/
public class ReflectAttackTest {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
Class clazz = singleton.getClass();
try {
Constructor<Singleton> constructor = clazz.getDeclaredConstructors()[0];
//設置允許訪問私有的構造器
constructor.setAccessible(true);
Singleton singleton1 = constructor.newInstance();
if (singleton1 != null && singleton1.getClass().equals(singleton.getClass())) {
System.out.println("通過反射構造除了對象");
} else {
return;
}
if (singleton == singleton1) {
System.out.println("是同一個對象");
} else {
System.out.println("是兩個不同的對象");
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
運行結果:
直接拋出了異常。
第二種
單例代碼:
package com.fine.reflect.enhance;
/**
* 單例
* 枚舉
*
* @author finefine at: 2019-05-02 22:22
*/
public enum SingletonEnum {
INSTANCE;
}
測試代碼:
package com.fine.reflect.enhance;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* @author finefine at: 2019-05-03 00:50
*/
public class SingletonEnumTest {
public static void main(String[] args) {
SingletonEnum singletonEnum = SingletonEnum.INSTANCE;
Class clazz = singletonEnum.getClass();
try {
Constructor<Singleton> constructor = clazz.getDeclaredConstructors()[0];
//設置允許訪問私有的構造器
constructor.setAccessible(true);
Singleton singleton1 = constructor.newInstance();
if (singleton1 != null && singleton1.getClass().equals(singletonEnum.getClass())) {
System.out.println("通過反射構造除了對象");
} else {
return;
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
運行結果:
拋出了java.lang.IllegalArgumentException: Cannot reflectively create enum objects at java.lang.reflect.Constructor.newInstance(Constructor.java:417)