天天看點

為什麼說Java語言是安全的?

Java的安全好象是目前的程式設計語言中最優秀的一種,Java技術之是以适用于網絡就是因為它有完備的、設計到其結構中的安全模式。上面我們讨論過關于類裝載和命名空間等Java特有的屬性,那麼現在我們來看一看在實際的操作中這些特性是怎樣達到安全的。  

沙箱重新整理程式  

    Java安全模式的重點在于保護最終使用者不受從網上下載下傳的破壞性程式的幹擾。為達到這個目的,Java提供了一個專用的運作Java程式的沙箱。Java程式在它的沙箱内可做任何事情,但出此邊界就不能有任何操作。例如,未經确認的JavaApplet的沙箱禁止許多操作,其中包括:

禁止對本地磁盤的讀寫;

除了下載下傳此Applet的主機外不能與任何别的主機連接配接;

禁止建立一個新的程序;

禁止載入一個直接調用本地方法的新的動态庫。

        通過限定下載下傳代碼的可執行操作的範圍,Java安全模式可使使用者免受破壞性程式的威脅。 

類載入程式體系結構

        在安全沙箱中JVM的一個重要方面是其類載入程式結構。在JVM中,類載入程式負責輸入那些定義運作程式的類和接口的二進制資料。在圖1中隻有一塊被标記為“類載入程式”,但事實上,在JVM内部可能有多個類載入程式。可以說,圖中的類載入程式實際代表了一個可能涉及許多類載入程式的系統。JVM有非常靈活的類載入結構,它允許Java應用程式自己定義裝載類的方式。  

        Java應用能用二種類載入程式:“原始的”類載入程式和類載入程式對象。原始的類載入程式(隻有一個)是JVM的一部分。例如,如果JVM在某個作業系統上作為C程式被啟用,那麼原始的類載入程式就是那個C程式的一部分。原始的類載入程式裝載獲得确認的類,其中包括JavaAPI類,它們通常取自本地的硬碟。 

        在程式運作時Java應用裝入類載入程式對象,它們能以自定義的方式載入類,例如通過網絡來下載下傳類檔案。JVM認為它用原始的類載入程式裝入的任何一個類都是已經确認的,不管它是否是JavaAPI的一部分。然而它對那些通過類載入對象裝入的類則持另一種态度。在預設的情況下它認為它們是未獲确認的。雖然原始的類載入程式是虛拟機運作的本質部分,但類載入對象不是。恰恰相反,類載入程式是用Java寫的,編譯成類檔案,載入虛拟機中,然後象别的對象一樣被執行個體化。實際上它們隻是一個運作中的程式的部分執行代碼。圖2描述了這種結構。 

圖2Java類載入程式體系結構

        因為有了類載入程式對象,在編譯時你不必知道都有哪些類最終加入了Java應用程式。這樣,你能在運作時動态地擴充Java應用程式。當你運作應用程式時,它能判斷需要别的什麼類,然後通過一個或多個類載入程式對象來裝入它們。因為你是用Java編寫類載入程式的,是以你能用任何方式安裝類:可通過網絡下載下傳,從某些資料庫中取得,甚至在乘飛機時把它算出來。 

類載入程式和命名空間

JVM對每個它所載入的類都記下了是用哪種類載入程式裝入的,當一個被載入的程式引用另一個類時,虛拟機要求用同一個類載入程式裝入被引入的類。如果虛拟機用某一個類載入程式裝入了Volcano類,它将用同樣的類載入程式來裝入Volcano類引用的所有類。如果Volcano引用了一個叫Java的類,也許調用了Java類的方法,虛拟機就會向裝入Volcano類的載入程式要求獲得Java類。載入程式傳回的Java類與Volcano類是動态連結的。

        因為JVM用這種方法裝載類,在預設條件下某個類隻能看見用同一個類載入程式裝入的其它類。Java體系結構用這種方法在單個Java應用中建立多個命名空間。命名空間是一些由特定的類載入程式裝入的類的獨一無二的名字集合。JVM為每個類載入程式維護一個命名空間,所有由該類載入程式裝入的類的名字組成了這個命名空間。 

        例如:一旦某個JVM把一個叫Volcano的類裝入到某一特定的命名空間後,就不能再把别一Volcano類裝入那個命名空間。然而你可以把多個Volcano類裝入JVM,因為你隻要建立多個類載入程式就能在某個Java應用中建立多個命名空間。如果你在某個運作着的Java應用中建立了三個單獨的命名空間(三個類載入程式每個載入程式一個),那麼給每個命名空間裝入一個Volcano類,你的應用中就有三個不同的Volcano。 

        Java應用能使多個類載入程式對象執行個體化,不管它是否來自同一個類。是以它能根據需要建立多個類載入程式對象。用不同的類載入程式裝入的類在不同的命名空間中,并且除非明确許可外都不能互相通路。當你開發Java應用時,你可以把從不同來源載入的類隔離到不同的命名空間中。這樣用Java的類載入程式體系結構就可控制不同來源的代碼間的通路,你可以防止破壞性代碼的通路正常的代碼。 

Applet的類載入程式

        Web浏覽器是用類載入程式進行動态擴充的一個例子,它用類載入程式對象從網上為某個applet下載下傳類檔案。Web浏覽器啟動一個裝入類載入程式對象(通常稱作applet類載入程式)的Java應用,這種類載入程式對象知道怎樣從HTTP伺服器獲得類檔案。Applet是動态擴充的一個例子,因為當Java應用啟動時,它并不知道浏覽器會要它從網上下載下傳哪些類檔案。要下載下傳的類檔案是在運作中浏覽器遇上含有Javaapplet的網頁時決定的。 

        Web浏覽器啟動的Java應用通常為它索取類檔案的網上站點建立不同的applet類載入程式對象。因而不同來源的類檔案由不同的類載入程式對象裝入,并被放入主Java應用内不同的命名空間中。因為不同來源的applet類檔案放在隔離開的命名空間中,是以破壞性的代碼就不能與從其他來源下載下傳的代碼直接接觸。 

類載入程式間的合作

        通常情況下,類載入程式對象都是互相依賴以完成各自遇到的類載入需求,至少它們都依賴于原始的類載入程式。例如,假設你寫了個Java應用,它安裝了一個通過從網上下載下傳類檔案來擷取類的載入程式。假定在運作Java應用期間要求你的類載入程式裝入一個叫Volcano的類。一種實作方法是,首先讓原始的類載入程式在已經确認的類庫中尋找并裝入該類。由于Volcano不是JavaAPI的一部分,那麼原始的類載入程式就找不到它,這樣你的類載入程式就會用它自定義的方式從網上下載下傳Volcano類,假定你的類載入程式能下載下傳Volcano類,那麼它就可以在将來的應用程式的執行中發揮作 用。 

        下面繼續同一個例子,假定一段時間後Volcano類的某個方法第一次被調用,它引用了JavaAPI中的String類,由于這是第一次調用,虛拟機要求你的類載入程式(載入Volcano的那個)裝入String類。和前面一樣,你的類載入程式先把要求傳遞給原始的類載入程式,不過這一次原始的類載入程式能直接傳回String類,因為String類是很基本的類,它肯定已被用過,是以也已被裝入了。大多數情況下原始的類載入程式會傳回從正獲确認中預先裝入的String類。這樣,類載入程式就不會再從網上下載下傳它,而隻是把原始的類載入程式傳回的類傳遞給虛拟機。以後不管什麼時候Volcano類要引用String 類,虛拟機都會調用已載入的這個類。 

沙箱中的類載入程式

        在Java沙箱中,類載入程式體系結構是阻擋破壞性代碼的第一道防線。不過也正是類載入程式把可能造成破壞的代碼帶進JVM中。 

        類載入程式體系結構對Java水箱的作用有: 

它阻止了破壞性代碼幹擾良好的代碼。

它保護已獲得确認的類庫。

        類載入程式結構通過識别類是否獲得确認來保護類庫。如果某個破壞性的類能成功地欺騙JVM,使JVM相信它是來自于JavaAPI的已獲确認的類,那麼它就會突破沙箱的防線。而類載入程式結構能防止未獲确認的類模仿已獲得确認的類,這種預防辦法保障了Java運作時的安全。 

命名空間和保護屏

        類載入程式結構給不同的類載入程式裝入的類提供了受保護的命名空間,這阻止了破壞性的代碼幹擾正常的代碼。前面提到過,命名空間是JVM維護的被載入類的名字集合。 

        命名空間有助于安全性是因為你能在不同命名空間的類間放置保護屏。在JVM内部,同一個命名空間中的類能直接地互互相動,但不同命名間中的類甚至不知道對方的存在,除非明确地提供允許通路的機制。如果某個破壞性的類被裝入後獲得了對目前其它裝入類的通路權,那這個類就有可能知道它不該知道的東西,或有可能幹擾你的程式運作。 

建立一個安全的環境

        當你寫一個使用類載入程式的應用時,你就建立了一個運作被動态裝入的代碼的環境。如果你想讓它沒有漏洞,在編寫應用和類載入程式時必須遵守一些規則。總的來說要隔離破壞性的代碼和正常的代碼,并象保護JavaAPI一樣保護獲得确認的類庫。 

命名空間和代碼來源

        為了發揮命名空間對安全性的作用,你要確定用不同的類載入程式從不同的來源裝入類。這就是支援Java的Web浏覽器所采用的體制。Web浏覽器所啟動的Java應用通常為它從網上下載下傳類的每個來源建立不同的applet類載入程式對象。例如,某個浏覽器用一個類載入程式對象從http://www.riscapplets.com下載下傳類,而用另一個從http://www.meanapplets.com下載下傳類。 

保護受限制的包

        Java允許同一個包中的類互相授予某些通路權利,但不能對包外的類授予通路權。是以,如果你的類載入程式接到請求,要裝入一個從名字上看似是JavaAPI一部分的類(如名為Java.lang.virus的類),它就要小心處理這一請示。如果這樣的類被裝入的話,它就有權通路Java.lang這個已獲确認的類庫,進而有可能搞破壞。 

        是以,你應該正式地寫這樣一個類載入程式,它能很簡單地阻絕裝入那些看似是JavaAPI一部分(或任何其它已獲信任的庫),卻不在本地已獲确認的庫中的類。換名說,當你的類載入程式把請示傳遞給原始的類載入程式後,後者表示它不能裝入這個類,你的類載入程式就應檢視一下這個類有沒有聲明為某個已獲确認的包中的一員。如果它聲明了,那你的類載入程式就應該發出安全性異常消息,而不是從網上下載下傳它。 

保護被禁止的包

        此外,你也許在獲得信任的庫中安裝了一些包,你隻希望你的應用通過原始的類載入程式來裝入其中的類,而不想讓那些你的類載入程式裝入的類有通路權利。例如,假定你已經建立了一個叫absolutepower的包,并把它裝到原始的類載入程式有權通路的本地庫中,再假定你不希望自己的類載入程式裝入的類能裝入absolutepower包中的任何一個類。在這種情況下,你所寫的類載入程式要做的第一件事是確定所需要的類沒有聲明是absolutepower包中的一員。如果這樣的類有需求的話,你的類載入程式就應發出安全性異常消息,而不是把類名傳給原始的類載入程式。 

        類載入程式區分某個類是來自受限制的包還是來自被禁止的包的唯一辦法是看它的名字。是以必須給類載入程式一張受限制的和被禁止的包的名單。因為類名java.lang.virus表示它是來自java.lang的包,而java.lang是受限制的包,是以如果原始的類載入程式不能載入它的話,你的類載入程式就應該發出一條安全異常消息。因為類名absolutepwer.FaneyClassloader說明它屬于被禁止的包absolutepwer,你的類載入程式也應該發出安全異常消息。 

注重安全性的類載入程式

        寫注重安全性的類載入程式的方法一般是用以下四步: 

如果有類載入程式無權通路的類,那麼類載入程式就檢查需要的類是否屬于上面提到的被禁止的包。如果是,就發一條安全性異常消息,如果不是則繼續第二步;

類載入程式把要求轉給原始的類載入程式,如果它成功地傳回了類,類載入程式就傳回它,否則的話繼續第三步;

如果有獲得信任的包禁止類載入程式加入這個類,那麼類載入程式就檢查所要求的類是否屬于受限制的包,如果是,它就發安全異常消息,否則繼續第四步;

類載入程式最後試着用自定義的方法裝入類,例如從網上下載下傳。如果成功的話就傳回這個類,否則報告有“沒有發現類定義”的錯誤。

        類載入程式執行上述第一步和第三步保護了已獲确認的包。第一步徹底防止了裝入被禁止的包中的類,第三步禁止未獲得确認的類把它自己添加到已獲信任的包中。 

結論

        類載入程式的體系結構用二種方法幫助建立Java的安全模式: 

把代碼分離到不同的命名空間并在不同命名空間的代碼間設定保護屏;

保護象JavaAPI這樣已獲确認的庫。

        為了發揮Java類載入程式體系結構在安全性方面的作用,程式員們必須正确使用它的上述二種功能。為利用命名空間所形成的保護,不同來源的代碼應用不同的類載入程式對象來裝入;為利用受信任的包得到的保護,編寫的類載入程式必須對照受限制的和被禁止的包的名單來檢查所需求的類的命名

        在程式運作時Java應用裝入類載入程式對象,它們能以自定義的方式載入類,例如通過網絡來下載下傳類檔案。JVM認為它用原始的類載入程式裝入的任何一個類都是已經确認的,不管它是否是JavaAPI的一部分。然而它對那些通過類載入對象裝入的類則持另一種态度。在預設的情況下它認為它們是未獲确認的。雖然原始的類載入程式是虛拟機運作的本質部分,但類載入對象不是。恰恰相反,類載入程式是用Java寫的,編譯成類檔案,載入虛拟機中,然後象别的對象一樣被執行個體化。實際上它們隻是一個運作中的程式的部分執行代碼。