Java安全模型側(cè)重于保護終端用戶免受從網(wǎng)絡(luò)下載的净赴、來自不可靠來源的火诸、惡意程序(以及善意程序中的bug)的侵犯该窗。為了達到這個目的酌心,Java提供了一個用戶可配置的“沙箱”拌消,在沙箱中可以放置不可靠的Java程序。
2.1 基本沙箱
沙箱模型使你可以接收來自任何來源的代碼安券,而不是要求用戶避免將來自不受信站點的代碼下載到機器上墩崩。
組成Java沙箱的基本組件如下:
- 類裝載器結(jié)構(gòu)
- class文件檢驗器
- 內(nèi)置于Java虛擬機(及語言)的安全特性
- 安全管理器及Java API
2.2 類裝載器體系結(jié)構(gòu)
在Java沙箱中,類裝載器是第一道防線:
- 它防止惡意代碼去干涉善意的代碼
- 它守護了被信任的類庫的邊界
- 它將代碼歸入某類(稱為保護域)侯勉,該類確定了代碼可以進行哪些操作
類裝載器體系結(jié)構(gòu)可以防止惡意的代碼去干涉善意的代碼鹦筹,是通過為由不同的類裝載器裝入的類提供不同的命名空間來實現(xiàn)的。命名空間由一系列唯一的名稱組成址貌,每一個被裝載的類有一個名字铐拐,這個命名空間是由Java虛擬機為每一個類裝載器維護的。
類裝載器體系結(jié)構(gòu)守護了被信任的類庫的邊界练对,是通過分別使用不同的類裝載器裝載可靠的包和不可靠的包來實現(xiàn)的遍蟋。
從Java 1.2版本開始,類裝載器請求另一個用戶自定義類裝載器來裝載一個類型的過程被形式化锹淌,稱為雙親委派模式匿值。除啟動類裝載器外的每一個類裝載器,都有一個“雙親”類裝載器赂摆,在某個特定的類裝載器試圖以常用方式裝載類型之前挟憔,它會先默認地將這個任務(wù)“委派”給它的雙親——請求它的雙親來裝載這個類型钟些。
運行時包,是指由同一個類裝載器裝載的绊谭、屬于同一個包的政恍、多個類型的集合。在允許兩個類型之間對包內(nèi)可見的成員進行訪問前达传,虛擬機不但要確定這兩個類型屬于同一個包篙耗,還必須確認它們屬于同一個運行時包——它們必須是由同一個類裝載器裝載的。
除了屏蔽不同命名空間的類以及保護受信任的類庫的邊界外宪赶,類裝載器還起到了另外的安全作用宗弯。類裝載器必須將每一個被裝載的類放置在一個保護域中,一個保護域定義了這個代碼在運行時將得到怎樣的權(quán)限搂妻。
2.3 class文件檢驗器
和類裝載器一起蒙保,class文件檢驗器保證裝載的class文件內(nèi)容有正確的內(nèi)部結(jié)構(gòu),并且這些class文件相互間協(xié)調(diào)一致欲主。如果class文件檢驗器在class文件中發(fā)現(xiàn)了問題邓厕,它會拋出異常。
JVM的class文件檢驗器只在字節(jié)碼執(zhí)行之前進行一次分析扁瓢,需要進行四趟獨立的掃描:
- 第一趟是在類被裝載時進行的详恼,主要檢查class文件的內(nèi)部結(jié)構(gòu)
- 第二趟和第三趟是在連接過程中進行的,主要確認類型數(shù)據(jù)遵從Java編程語言的語義引几,包括檢驗它所包含的所有字節(jié)碼的完整性昧互。
- 第四趟是在動態(tài)連接的過程中解析符號時進行的,主要確認被引用的類她紫、字段以及方法確實存在硅堆。
2.4 JVM中內(nèi)置的安全特性
JVM在執(zhí)行字節(jié)碼時還進行其他一些內(nèi)置的安全機制的操作屿储,包括:
- 類型安全的引用轉(zhuǎn)換
- 結(jié)構(gòu)化的內(nèi)存訪問(無指針算法)
- 自動垃圾收集(不必顯式地釋放被分配的內(nèi)存)
- 數(shù)組邊界檢查
- 空引用檢查
2.5 安全管理器和Java API
Java安全模型的前三個組成部分——類裝載器體系結(jié)構(gòu)贿讹、class文件檢驗器以及Java中內(nèi)置的安全特性——一起達到一個共同的目的:保持JVM的實例和它正在運行的應(yīng)用程序的內(nèi)部完整性,使得它們不被下載的惡意或有漏洞的代碼侵犯够掠。相反民褂,這個安全模型的第四個組成部分是安全管理器,它主要用于保護虛擬機的外部資源不被虛擬機內(nèi)運行的惡意或有漏洞的代碼侵犯疯潭。
安全管理器定義了沙箱的外部邊界赊堪。因為它是可定制的,所以它允許為程序建立自定義的安全策略竖哩。
Java應(yīng)用程序在啟動時不會默認安裝安全管理器哭廉,也不會有任何安全限制。在Java 1.2版本以前相叁,如果安裝了安全管理器遵绰,那么它將負責(zé)應(yīng)用程序整個剩余的生命周期辽幌,不能被替代、擴展或者修改椿访。在1.2版本中乌企,當(dāng)前安裝的安全管理器能夠在允許的情況下被替換。
安全管理器主要負責(zé)兩個方面的工作:說明一個安全策略以及執(zhí)行這個安全策略成玫。
Java 1.2版本中的java.lang.SecurityManager
是一個具體的類加酵,它提供了一個默認的安全管理器的實現(xiàn),能夠輔助建立一個基于代碼簽名的細粒度的安全策略哭当。用戶可以顯示實例化這個安全管理器猪腕,或者讓它自動安裝。例如钦勘,在Sun的Java 2 SDK版本1.2中码撰,可以在命令行使用-Djava.security.manager
選項來指明安裝具體安全管理器。
具體安全管理器類允許用戶不用Java代碼定義自己的安全策略个盆,而是用一個稱為策略文件的ASCII文件脖岛。在策略文件中,可以給代碼來源授予權(quán)限颊亮。權(quán)限是用類定義的柴梆,它是java.security.Permission
的子類。代碼來源是由代碼庫的URL和一些簽名組成的终惑,從這個URL可以裝載代碼绍在,而簽名則為這個代碼作擔(dān)保。當(dāng)創(chuàng)建安全管理器時雹有,它對策略文件進行解析偿渡,并創(chuàng)建CodeSource(代碼來源)和Permission(權(quán)限)對象,這些對象被封裝在一個單獨的Policy對象中霸奕,這個Policy對象就代表了運行時的策略溜宽。任何時刻只能有一個Policy對象被安裝。
類裝載器將類型放到保護域(ProtectionDomain)中质帅,保護域封裝了授予代碼來源的所有權(quán)限适揉,這些代碼來源由裝載的類型代表。
當(dāng)具體安全管理器的check方法被調(diào)用時煤惩,它們中的大多數(shù)都將請求傳遞給一個稱為AcessController的類嫉嘀。這個AccessController使用了包含在保護域?qū)ο笾械男畔ⅲ@個對象所屬的類的方法在調(diào)用棧中魄揉,AccessController進行棧檢查以確定這個操作能否被執(zhí)行剪侮。
在 java.lang.SecurityManager
中有兩個關(guān)鍵方法:
- checkPermission(Permission perm) —— 進行某個操作(它需要指定的權(quán)限)前被調(diào)用
- checkPermission(Permission perm, Object context) —— 在被傳遞的安全上下文中進行某個操作(它需要指定的權(quán)限)前被調(diào)用
在具體安全管理器類中,checkPermission( )方法同樣負責(zé)決定洛退,是否允許將某個操作的任務(wù)委派給另一個方法瓣俯。這個checkPermission( )方法只是簡單地調(diào)用了類java.security.AccessController
中的靜態(tài)checkPermission( )方法红淡,并將這個Permission對象傳遞給它。
2.6 代碼簽名和認證
Java安全模型很重要的一點就是它支持認證降铸。認證可以使用戶確認在旱,由某些團體擔(dān)保的一些class文件是值得信任的,并且這些class文件在到達用戶虛擬機的途中沒有被改變推掸。這樣桶蝎,如果用戶在一定程度上信任這個為代碼作擔(dān)保的團體,也就可以在一定程度上簡化沙箱對這段代碼的限制谅畅〉窃可以對由不同團體簽名的代碼建立不同的安全限制。
2.7 策略
Java安全體系結(jié)構(gòu)的真正好處在于毡泻,它可以對代碼授予不同層次的信任度來部分地訪問系統(tǒng)胜茧。
版本1.2的安全體系結(jié)構(gòu)的主要目標之一就是使建立(以代碼簽名為基礎(chǔ)的)細粒度的訪問控制策略的過程更為簡單且更少出錯。在版本1.2的安全模型中仇味,權(quán)限(系統(tǒng)訪問權(quán)限)使授予代碼來源的呻顽。
對應(yīng)于整個Java應(yīng)用程序的一個訪問控制策略是由抽象類java.security.Policy的一個子類的單個實例所表示的。在任何時候丹墨,每個應(yīng)用程序?qū)嶋H上都只有一個Policy對象廊遍。類裝在其利用這個Policy對象來幫助它們決定,在把一段代碼導(dǎo)入虛擬機時應(yīng)該給它們怎么樣的權(quán)限贩挣。
安全策略是一個從描述運行代碼的屬性集合到這段代碼所擁有的權(quán)限的映射喉前。在版本1.2的安全體系結(jié)構(gòu)中,描述運行代碼的屬性被總稱為代碼來源王财。
在版本1.2中卵迂,所有和具體安全管理器有關(guān)的工具和訪問控制體系結(jié)構(gòu)都只對證書起作用,而不能對“赤裸”的公鑰起作用绒净。
權(quán)限是由抽象類java.security.Permission
的一個子類的實例表示的见咒。一個Permission對象有三個屬性:類型、名字和可選的操作疯溺。
在Policy對象中论颅,每一個CodeSource是和一個或多個Permission對象相關(guān)聯(lián)的。
策略文件
由Sun提供的在Java 1.2平臺下的具體Policy子類采用如下方法:在一個ASCII策略文件中用上下文無關(guān)文法描述安全策略囱嫩。
keystore "ijvmkeys";
grant signedBy "friend" {
permission java.io.FilePermission "question.txt", "read";
permission java.io.FilePermission "answer.txt", "read";
};
grant signedBy "stranger" {
permission java.io.FilePermission "question.txt", "read";
};
grant codeBase "file:${com.artima.ijvm.cdrom.home}/security/ex2/*" {
permission java.io.FilePermission "question.txt", "read";
permission java.io.FilePermission "answer.txt", "read";
};
最后一條grant語句中的代碼庫URL采用了文件的形式:它包含了一個屬性${com.artima.ijvm.cdrom.home}
。
2.8 保護域
當(dāng)類裝載器將類型裝入JVM時漏设,它們將為每個類型指派一個保護域墨闲。保護域定義了授予一段特定代碼的所有權(quán)限。(一個保護域?qū)?yīng)于策略文件中的一個或多個grant子句郑口。)裝載入JVM的每一個類型都屬于且僅屬于一個保護域鸳碧。
雖然前面的Policy對象代表了一個從代碼來源到權(quán)限的全局映射盾鳞,但是最終還是由類裝載器負責(zé)決定代碼執(zhí)行時將獲得怎樣的權(quán)限。
圖2-4用圖形化的方式描述了保護域瞻离、代碼來源以及權(quán)限腾仅。ProtectionDomain對象封裝了一個到CodeSource對象的引用以及一個到java.security.Permissions
對象的引用。java.security.Permissions
是抽象類java.security.PermissionCollection
的一個具體類套利,代表了一個同構(gòu)權(quán)限的集合推励。
當(dāng)一個類裝載器將Friend和Friend$1
導(dǎo)入方法區(qū)時肉迫,類裝載器將把一個ProtectionDomain對象的引用和這些class文件的字節(jié)傳遞給defineClass( )方法验辞。這個defineClass( )方法將Friend和Friend$1
所在的方法區(qū)中的類型數(shù)據(jù)和被傳遞的ProtectionDomain對象相關(guān)聯(lián)。
2.9 訪問控制器
類java.security.AccessController
提供了一個默認的安全策略執(zhí)行機制喊衫,它使用棧檢查來決定潛在不安全的操作是否被允許跌造。
由AccessController的checkPermission( )實現(xiàn)的基本算法決定了調(diào)用棧中的每個幀是否有權(quán)執(zhí)行潛在不安全的操作。每一個棧幀代表了由當(dāng)前線程調(diào)用的某個方法族购,每一個方法是在某個類中定義的壳贪,每一個類又屬于某個保護域,每個保護域包含一些權(quán)限寝杖。因此撑碴,每個棧幀間接地和一些權(quán)限相關(guān)。
當(dāng)調(diào)用doPrivileged( )方法時朝墩,就像調(diào)用其它任何方法一樣醉拓,都會將一個新的棧幀壓入棧。在由AccessController執(zhí)行的棧檢查中收苏,一個doPrivileged( )方法調(diào)用的棧幀標識了檢查過程的提前終止點亿卤。如果和調(diào)用doPrivileged( )的方法相關(guān)聯(lián)的保護域擁有執(zhí)行被請求操作的權(quán)限,AccessController將立即返回鹿霸。這樣這個操作就被允許排吴,即使在棧下層的代碼可能沒有執(zhí)行這個操作的權(quán)限。