單例模式的反射漏洞和反序列化漏洞
除了枚舉式單例模式外,其餘4種在單例模式提到的單例模式的實作方式都存在反射漏洞和反序列化漏洞。
package singleton;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
/**
* 用反射和反序列化的方法破解單例模式
* @author weiyx15
*
*/
public class SingletonCrack {
public static void main(String[] args) throws Exception
{
// 正常建立單例對象
SingletonLazy s1 = SingletonLazy.getInstance();
SingletonLazy s2 = SingletonLazy.getInstance();
System.out.println(s1);
System.out.println(s2);
// 用反射破解單例
Class<SingletonLazy> cls = (Class<SingletonLazy>) Class.forName("singleton.SingletonLazy"); // 擷取SingletonLazy類
Constructor<SingletonLazy> cons = cls.getDeclaredConstructor(null); // 擷取SingletonLazy的構造方法
cons.setAccessible(true); // 跳過方法的可見性檢查
SingletonLazy s3 = cons.newInstance(); // 調用構造方法生成新對象
SingletonLazy s4 = cons.newInstance(); // 調用構造方法生成新對象
System.out.println(s3);
System.out.println(s4);
// 用反序列化破解單例
FileOutputStream fos = new FileOutputStream("object.out"); // 檔案輸出流
ObjectOutputStream oos = new ObjectOutputStream(fos); // 對象輸出流
oos.writeObject(s1); // 向檔案序列化對象
oos.close(); // 關閉對象輸出流
fos.close(); // 關閉檔案輸出流
FileInputStream fis = new FileInputStream("object.out"); // 檔案輸入流
ObjectInputStream ois = new ObjectInputStream(fis); // 對象輸入流
SingletonLazy s5 = (SingletonLazy) ois.readObject(); // 從檔案反序列化對象
ois.close(); // 關閉對象輸入流
fis.close(); // 關閉檔案輸入流
System.out.println(s5);
}
}
運作結果
singleton.SingletonLazy@15db9742 // s1
singleton.SingletonLazy@15db9742 // s2
singleton.SingletonLazy@6d06d69c // s3
singleton.SingletonLazy@7852e922 // s4
singleton.SingletonLazy@3b07d329 // s5
從運作結果可以看到,通過反射可以得到私有構造方法,進而執行個體化兩個不同的對象執行個體{@code [email protected]}和{@code [email protected]}. 通過反序列化,也可以得到新對象{@code [email protected]}.
以懶漢式單例模式的實作為例,解決反射漏洞和反序列化漏洞的方法如下:
package singleton;
import java.io.ObjectStreamException;
import java.io.Serializable;
/**
* 排除了反射漏洞和反序列化漏洞的懶漢式單例模式
* @author weiyx15
*
*/
public class SingletonLazySafe implements Serializable{
private static SingletonLazySafe instance;
private SingletonLazySafe() {
// 防止反射漏洞通過再次調用私有構造方法執行個體化新的instance
if (instance != null)
{
throw new RuntimeException(); // 抛出運作時異常
}
}
public static synchronized SingletonLazySafe getInstance() {
if (instance == null) // 如果未執行個體化,則先執行個體化
{
instance = new SingletonLazySafe(); // 調用getInstance方法後再執行個體化對象
}
return instance;
}
/**
* 從I/O流讀取對象時會調用readResolve接口
* 在readResolve接口中直接傳回instance對象
* 避免反序列化時重新執行個體化對象
* @return 單例對象
* @throws ObjectStreamException
*/
private Object readResolve() throws ObjectStreamException {
return instance;
}
}