單例模式在很多Java程序員的眼中,應(yīng)該是設(shè)計(jì)模式里最簡(jiǎn)單的一種了。那么單例模式可能會(huì)被攻擊,您聽說(shuō)過(guò)么?
說(shuō)到“單例模式被攻擊”這個(gè)話題,大家最容易想到的可能就是通過(guò)序列化/反序列化來(lái)攻擊單例模式,因?yàn)橐粋€(gè)對(duì)象實(shí)例序列化再反序列化后,得到的新的對(duì)象雖然各字段內(nèi)容和原字段一致,然而對(duì)象地址和原始對(duì)象地址相比已經(jīng)發(fā)生了變化,因此它們是兩個(gè)不同的對(duì)象。
上面的結(jié)論完全正確,然而除了序列化/反序列化,單例模式還可能遭受另一種方式的攻擊,即反射攻擊(Reflection attack)。
看一個(gè)具體例子:
public class JerrySingleton { private String name; private JerrySingleton(){ name = "Jerry";}private static class SingletonHolder{ private static final JerrySingleton INSTANCE = new JerrySingleton();}public static JerrySingleton getInstance() { return SingletonHolder.INSTANCE; }}
上面是一個(gè)餓漢式單例。
然而我只需要將這個(gè)單例類JerrySingleton的構(gòu)造函數(shù)通過(guò)反射設(shè)置成可以訪問(wèn)Accessible,然后就能通過(guò)反射調(diào)用該構(gòu)造函數(shù),進(jìn)而生成新的對(duì)象實(shí)例。這樣就破壞了單例模式。
Class<?> classType = JerrySingleton.class;Constructor<?> c = classType.getDeclaredConstructor(null);c.setAccessible(true);JerrySingleton e1 = (JerrySingleton)c.newInstance();JerrySingleton e2 = JerrySingleton.getInstance();System.out.println(e1 == e2);
第6行代碼會(huì)打印false。
針對(duì)這種攻擊,一種可行的防御措施是在單例類的構(gòu)造函數(shù)內(nèi)定義一個(gè)布爾變量,初始化為false。當(dāng)構(gòu)造函數(shù)執(zhí)行后,該變量被置為true。如果接下來(lái)構(gòu)造函數(shù)再次被執(zhí)行,則人為拋出異常,避免構(gòu)造函數(shù)重復(fù)執(zhí)行。
public class JerrySingletonImproved { private static boolean flag = false; private JerrySingletonImproved(){ synchronized(JerrySingletonImproved.class) { if(flag == false) { flag = !flag; } else { throw new RuntimeException("Singleton violated"); } }}}
這種防御措施無(wú)法從根本上杜絕Singleton被攻擊,因?yàn)楣粽呷耘f可以通過(guò)反射來(lái)修改布爾變量flag的值,從而繞過(guò)這個(gè)檢查。
最理想的不會(huì)受到攻擊的單例模式實(shí)現(xiàn)是借助Java里枚舉類Enumeration的特性:
這種實(shí)現(xiàn)類型的單例模式的消費(fèi)代碼:
System.out.println("Name:" + JerrySingletonAnotherApproach.INSTANCE.getName());
如果攻擊者通過(guò)前面介紹的反射代碼對(duì)這種實(shí)現(xiàn)方式的單例進(jìn)行攻擊,JDK會(huì)拋出NoSuchMethodException異常:
Exception in thread "main" java.lang.NoSuchMethodException: singleton.JerrySingletonAnotherApproach.<init>()at java.lang.Class.getConstructor0(Class.java:3082)at java.lang.Class.getDeclaredConstructor(Class.java:2178)at singleton.SingletonAttack.test3(SingletonAttack.java:31)at singleton.SingletonAttack.main(SingletonAttack.java:43)
究其原因,是因?yàn)楝F(xiàn)在我們是通過(guò)Java枚舉方式實(shí)現(xiàn)的單例,枚舉類沒(méi)有傳統(tǒng)意義上的構(gòu)造函數(shù),因此對(duì)這種反射攻擊免疫。
要獲取更多Jerry的原創(chuàng)技術(shù)文章,請(qǐng)關(guān)注公眾號(hào)"汪子熙"
聯(lián)系客服