Java 授权内幕--转载
在信息安全性領(lǐng)域,授權(quán)是世界的的中心,因?yàn)樗强刂苽€(gè)體(即人、進(jìn)程和計(jì)算機(jī))對(duì)系統(tǒng)資源的訪問(wèn)權(quán)限的過(guò)程。直到最近,在 Java 安全體系結(jié)構(gòu)中相關(guān)的問(wèn)題都是“這段運(yùn)行中的代碼的訪問(wèn)權(quán)限是什么?” 隨著 Java 認(rèn)證和授權(quán)服務(wù)(Java Authentication and Authorization Service,JAAS)的引入,這種情況改變了。JAAS 首先是作為 JDK 版本 1.3 的平臺(tái)擴(kuò)展,之后作為 JDK 1.4 及以后版本的核心部分。在 JAAS 中,相關(guān)問(wèn)題變成了“運(yùn)行這段代碼的認(rèn)證用戶的訪問(wèn)權(quán)限是什么?”
在本文中,將同時(shí)介紹老的以代碼為中心的 Java 授權(quán)體系結(jié)構(gòu)和新的以用戶為中心的體系結(jié)構(gòu)。我將首先對(duì) Java 2 平臺(tái)安全體系結(jié)構(gòu)作一概述,重點(diǎn)放在這個(gè)體系結(jié)構(gòu)如何利用兩個(gè)基本概念 -- 安全策略和保護(hù)域 -- 來(lái)定義、組織和聚集靜態(tài)和/或動(dòng)態(tài)訪問(wèn)權(quán)限。然后詳細(xì)分析 Java 2 平臺(tái)安全體系結(jié)構(gòu)的運(yùn)行時(shí)訪問(wèn)檢查功能的底層機(jī)制,包括堆棧檢查和確定是否授予權(quán)限的遍歷(traversal)機(jī)制。在了解了以代碼為中心的授權(quán)模型是如何工作的后,我將轉(zhuǎn)向 Java 授權(quán)和認(rèn)證服務(wù)(JAAS)的以用戶為中心的授權(quán)模型。在這里,我將重點(diǎn)放到基于 subject 的訪問(wèn)控制這一概念上,并展示在 JAAS 中,它是如何在原來(lái) Java 2 平臺(tái)安全體系結(jié)構(gòu)的堆棧檢查機(jī)制之上實(shí)現(xiàn)的。
注意,本文假定讀者熟悉 Java 平臺(tái)(J2SE SDK 1.4)上的應(yīng)用程序編程,以及企業(yè)應(yīng)用程序安全性的基本概念。與 Java 平臺(tái)的以代碼為中心和以用戶為中心的授權(quán)體系結(jié)構(gòu)的所有概念都只作為介紹。
以代碼為中心的授權(quán)
Java 平臺(tái)傳統(tǒng)上是用來(lái)運(yùn)行移動(dòng)代碼的,如 applet。為了保護(hù)系統(tǒng)資源不被這些從網(wǎng)絡(luò)上下載到用戶瀏覽器中的任意代碼片段所破壞,applets 被限制到一個(gè)沙箱中,它們?cè)谶@里以有限的一組權(quán)限運(yùn)行 。另一方面,對(duì)于本地 Java 應(yīng)用程序,很少會(huì)(如果會(huì)的話)安裝將提供類似沙箱環(huán)境的安全管理器。因此,本地應(yīng)用程序通常受到信任可以訪問(wèn)所有系統(tǒng)資源。
JDK 1.x 模型和 Java 2 平臺(tái) SDK 版本 1.2 的新安全結(jié)構(gòu)之間的最大區(qū)別是引入了新的、可配置的安全策略,這樣就可以實(shí)現(xiàn)細(xì)化的和可管理的訪問(wèn)控制。所有代碼(不管是本地還是下載的,不管是簽名或者沒(méi)有簽名的)都可以受到定義良好的安全策略的約束,它為不同的代碼授予(可能是重疊的)權(quán)限。同時(shí),隨著在 JVM 中引入了多進(jìn)程能力(請(qǐng)參閱?參考資料),出現(xiàn)了對(duì)基于用戶的訪問(wèn)控制的要求。
Java 2 平臺(tái)安全體系結(jié)構(gòu)背后的基本原理可以總結(jié)如下:一個(gè)系統(tǒng)級(jí)的?安全策略定義了按?保護(hù)域組織的執(zhí)行代碼的?訪問(wèn)權(quán)限(按照應(yīng)用程序的需要)。安全策略用于訪問(wèn)控制檢查,這是由 JVM 在運(yùn)行時(shí)執(zhí)行的。在本次導(dǎo)游中,我將逐一詳細(xì)闡述這些概念。
訪問(wèn)權(quán)限作為類型化(typed)對(duì)象
在 Java 2 平臺(tái)安全體系結(jié)構(gòu)中,所有訪問(wèn)權(quán)限都是類型化的并且有層次結(jié)構(gòu),其根是抽象類?java.security.Permission?。通常一個(gè)Permission?包含一個(gè)目標(biāo)(“由這個(gè)權(quán)限控制的操作將對(duì)誰(shuí)執(zhí)行?”)和一個(gè)操作(“如果這個(gè)權(quán)限允許的話,對(duì)這個(gè)目標(biāo)將執(zhí)行什么操作?”)。
在允許一段運(yùn)行的代碼對(duì)特定的“目標(biāo)”執(zhí)行特定的“操作”這一上下文中,一個(gè)重要的概念是代碼不一定被授予與所需要完全一樣的?Permission。相反,只要可以從實(shí)際授予這段代碼的?Permission?中推斷出或者隱含了所需要的?Permission?就可以。例如,如果一段運(yùn)行代碼授予了讀目錄 /x 中所有文件的權(quán)限,那么它就不需要對(duì)目標(biāo)文件 /x/in.xtx 執(zhí)行?讀操作的顯式權(quán)限,因?yàn)榍耙粋€(gè)權(quán)限隱含了后者。
顯然,某個(gè)?Permission?是否隱含另一個(gè)?Permission?的定義將取決于這兩個(gè)?Permission?是如何定義的。至少,這兩個(gè)?Permission?必須為同一類型。不過(guò),不能指望運(yùn)行時(shí)進(jìn)行進(jìn)一步的判斷,并且必須將這種隱含推斷邏輯指派給所涉及的?Permission?類。運(yùn)行時(shí)通過(guò)調(diào)用一個(gè)恰當(dāng)?shù)厝∶麨?implies?的方法來(lái)查詢?Permission?類的隱含推斷邏輯。
聚集的權(quán)限
新安全體系結(jié)構(gòu)也引入了?聚集(aggregation)的概念。在 Java 2 平臺(tái)上,可以聚集同一類型的?Permission?對(duì)象的多個(gè)實(shí)例。一組這種類型稱為?PermissionCollection?。例如,一個(gè)?PermissionCollection?可能包含兩個(gè)?java.io.FilePermission?實(shí)例,表示讀取兩個(gè)不同文件的特權(quán)。
這樣的類型化對(duì)象干凈地封裝了創(chuàng)建和維護(hù)一個(gè)集合并遍歷這一集合的功能。不用在每次要檢查權(quán)限時(shí)對(duì)每一個(gè)對(duì)象分別調(diào)用?implies()?方法,Java 運(yùn)行時(shí)只是調(diào)用由?PermissionCollection?對(duì)象提供的?implies()?方法并等待其響應(yīng)。可以為所創(chuàng)建的每一個(gè)自定義?Permission?對(duì)象定義一種新的?PermissionCollection?類型。當(dāng)然,?PermissionCollection?中?implies()?方法的具體實(shí)現(xiàn)取決于給定?Permission?對(duì)象的特性。
Permissions 對(duì)象
除了對(duì)特定的?Permission?類型有多個(gè)實(shí)例,任何給定的一段運(yùn)行代碼都將得到不同類型的?Permission?。 Java 2 平臺(tái)安全體系結(jié)構(gòu)為此以Permissions?對(duì)象的形式提供了一組?PermissionCollection?對(duì)象。一個(gè)?Permissions?對(duì)象是單個(gè)?Permission?實(shí)例的?集合的?集合。Permissions?類還提供了一個(gè)?implies()?方法。不過(guò),要記住為了讓一個(gè)?Permission?隱含另一個(gè),它們必須是同一類型的。因此,調(diào)用Permissions?對(duì)象的?implies()?方法會(huì)使后者首先在其內(nèi)部集合中定位正確的?PermissionCollection?實(shí)例(那個(gè)包含一組正確類型的Permission?對(duì)象的實(shí)例),然后調(diào)用由此獲得的?PermissionCollection?對(duì)象的?implies()?方法,并向它傳遞要檢查的?Permission?。
安全策略和保護(hù)域
適用于一個(gè)系統(tǒng)的安全策略實(shí)質(zhì)上是一個(gè)良好定義的“倉(cāng)庫(kù)”,它存儲(chǔ)了授予這個(gè)系統(tǒng)中不同實(shí)體的訪問(wèn)權(quán)限的斷言。根據(jù)?保護(hù)域(protection domain)的經(jīng)典定義(請(qǐng)參閱?參考資料),域是由系統(tǒng)中當(dāng)前獲得授權(quán)的一個(gè)實(shí)體可以直接訪問(wèn)的一組對(duì)象所界定的(按照這個(gè)定義,實(shí)際上可以將 JDK 版本 1.1 中的 Java 沙箱想像為一個(gè)具有固定邊界的保護(hù)域)。在此基礎(chǔ)上構(gòu)建的 Java 2 平臺(tái)安全策略設(shè)計(jì)為根據(jù)ProtectionDomain?授權(quán)訪問(wèn)權(quán)限,而不是向單個(gè)的一段運(yùn)行代碼授權(quán)這種權(quán)限。因此,每一個(gè)類或者對(duì)象“屬于”一個(gè)?ProtectionDomain?,安全策略對(duì)這個(gè)保護(hù)域授予了某種訪問(wèn)權(quán)限。重申?ProtectionDomain?的觀點(diǎn),一個(gè)特定的?ProtectionDomain?封裝了一組類(例如,所有從特定位置上裝載、并用特定密鑰簽名的所有類),它們的實(shí)例將會(huì)授予同樣的一組權(quán)限。
這種間接性(即,權(quán)限不是直接授予類和對(duì)象)背后的理由是可擴(kuò)展性 -- 它應(yīng)當(dāng)可以改變和/或細(xì)化構(gòu)成?ProtectionDomain?的定義,而不會(huì)影響權(quán)限的授予。(確實(shí),JAAS?之前的 ProtectionDomain?只由“屬于”它的代碼描述,而 JAAS?后的 ProtectionDomain?還由運(yùn)行代碼的、經(jīng)過(guò)認(rèn)證的用戶描述。由于每一位用戶都分配到了設(shè)置了他或者她的權(quán)限的特定?ProtectionDomain?,進(jìn)行用戶認(rèn)證可以使給定的一段代碼根據(jù)當(dāng)前認(rèn)證用戶而用不同的一組權(quán)限運(yùn)行。我將在討論 JAAS 授權(quán)體系結(jié)構(gòu)時(shí),對(duì)所有這些內(nèi)容給予更詳細(xì)的描述。)
保護(hù)域和代碼源
顯然,一定要能惟一地標(biāo)識(shí)一段運(yùn)行代碼以保證它的訪問(wèn)權(quán)限沒(méi)有沖突。運(yùn)行代碼的惟一標(biāo)識(shí)屬性共有兩項(xiàng):代碼的來(lái)源(代碼裝載到內(nèi)存所用的 URL)和代碼的 signer 實(shí)體(由對(duì)應(yīng)于運(yùn)行代碼的數(shù)字簽名的一組公共密鑰指定)。這兩種特性的組合在 Java 2 平臺(tái)安全體系結(jié)構(gòu)中編寫為給定運(yùn)行代碼的?CodeSource?。現(xiàn)在可以提供?ProtectionDomain?的更嚴(yán)格定義了:?ProtectionDomain?是一組?CodeSource?及其訪問(wèn)權(quán)限。換一種說(shuō)法,?ProtectionDomain?表示授予特定?CodeSource?的所有權(quán)限。
Java 運(yùn)行時(shí)通過(guò)名為?java.security.Policy?的類(的具體擴(kuò)展)設(shè)置?ProtectionDomain?與授予它的權(quán)限之間的映射。這個(gè)類的默認(rèn)擴(kuò)展是sun.security.provider.PolicyFile?。正如其名字所表明的,?sun.security.provider.PolicyFile?從一個(gè)文件中獲得?CodeSource?(由位置 URL 和 signer 標(biāo)識(shí)別名)與授予它的權(quán)限之間的映射。可以通過(guò)環(huán)境變量?java.security.policy?將這個(gè)文件的位置作為輸入提供給 JVM。?Policy?類提供了一個(gè)名為?getPermissions()?的方法,可以調(diào)用它以獲得授予特定?CodeSource?的一組權(quán)限。
SecureClassLoader
一個(gè)類與?其 ProtectionDomain?之間的映射是在類第一次裝載時(shí)設(shè)置的,并在類被垃圾收集之前不會(huì)改變。一個(gè)類通常是由一個(gè)名為SecureClassLoader?的特殊類裝載的。?SecureClassLoader?首先從相應(yīng) URL 處裝載字節(jié),如果需要還會(huì)驗(yàn)證包圍文檔文件的數(shù)字簽名。然后它調(diào)用上述?getPermissions()?方法獲得授予類的?CodeSource?的一個(gè)填充了靜態(tài)綁定權(quán)限的異類?PermissionCollection?。然后SecureClassLoader?創(chuàng)建新的?ProtectionDomain?,傳遞?CodeSource?及其相關(guān)的權(quán)限作為其構(gòu)造函數(shù)的參數(shù)(當(dāng)然,這假定對(duì)于給定CodeSource?還不存在?ProtectionDomain?。如果用一個(gè)現(xiàn)有的?CodeSource?裝載類,那么就會(huì)重復(fù)使用它已經(jīng)建立的?ProtectionDomain?)。?最后,用裝載的類字節(jié)向 JVM 定義一個(gè)類,并在關(guān)聯(lián)的?ProtectionDomain?中維護(hù)一個(gè)引用指針。
默認(rèn)情況下,會(huì)創(chuàng)建一個(gè)?ProtectionDomain?,并作為“特殊”情況處理,即屬于這個(gè)域的代碼被認(rèn)為是受信任的并可以獲得特殊的權(quán)限。這稱為?系統(tǒng)域并包括由?系統(tǒng)(應(yīng)用程序)裝載器、擴(kuò)展裝載器和?bootstrap 裝載器裝載的類。(有關(guān) Java 類裝載器的更多信息請(qǐng)參閱?參考資料。)
動(dòng)態(tài)權(quán)限
直到 Java 平臺(tái) 1.3,都只能用(上面描述的)以?CodeSource?和相關(guān)權(quán)限為參數(shù)的構(gòu)造函數(shù)創(chuàng)建?ProtectionDomain?。這意味著授予特定ProtectionDomain?的權(quán)限必須在構(gòu)建時(shí)就已經(jīng)知道,并且沒(méi)有動(dòng)態(tài)刷新所授予的一組權(quán)限的靈活性。然而在 Java 2 平臺(tái) SDK 1.4 中,ProtectionDomain?可以同時(shí)封裝(通過(guò)其構(gòu)造函數(shù)傳遞的)靜態(tài)權(quán)限和動(dòng)態(tài)權(quán)限。
動(dòng)態(tài)權(quán)限是在權(quán)限檢查時(shí)由生效的策略所授予的、并由?ProtectionDomain?隱式地處理。對(duì)?ProtectionDomain?調(diào)用?implies()?方法時(shí)(實(shí)質(zhì)上是對(duì)權(quán)限進(jìn)行檢查時(shí)),它調(diào)用安裝的?Policy?類的?getPolicyNoCheck()?方法。因而?Policy?類提供了刷新所授予的一組權(quán)限并向調(diào)用ProtectionDomain?返回這個(gè)刷新的權(quán)限的可能。這保證了針對(duì)在構(gòu)造時(shí)提供的?PermissionCollection?和在那一瞬間綁定的?Policy?的組合進(jìn)行權(quán)限檢查。
運(yùn)行時(shí)訪問(wèn)檢查
由一個(gè)名為?SecurityManager?的類負(fù)責(zé)實(shí)施系統(tǒng)安全策略。在默認(rèn)情況下不安裝安全管理器,必須通過(guò)一個(gè)在啟動(dòng)時(shí)傳遞給 JVM 的、名為java.security.manager?的環(huán)境變量顯式地指定。任何應(yīng)用程序都可找到安裝的?SecurityManager?并調(diào)用它相應(yīng)的?check<XXX>?方法。如果所要求的權(quán)限在給定運(yùn)行時(shí)上下文中是授予的,那么調(diào)用將無(wú)聲地返回。如果權(quán)限沒(méi)有授予,那么將拋出一個(gè)java.security.AccessControlException?。
在 Java 1.1 的時(shí)代,?SecurityManager?通過(guò)其內(nèi)部邏輯負(fù)責(zé)管理所有權(quán)限本身。因此,任何需要自定義邏輯進(jìn)行訪問(wèn)決定的應(yīng)用程序都必須實(shí)現(xiàn)并安裝一個(gè)自定義的?SecurityManager?。Java 2 平臺(tái)安全體系結(jié)構(gòu)通過(guò)引入一個(gè)名為?AccessController?的新類使這一切變得簡(jiǎn)單了,并更具有可擴(kuò)展性。這個(gè)類的目的與?SecurityManager?是一樣的,即它負(fù)責(zé)做出訪問(wèn)決定。當(dāng)然,?為了向后兼容性保留了 SecurityManager?類,但是其更新的實(shí)現(xiàn)委派給了底層的?AccessController?。對(duì)?SecurityManager?類進(jìn)行的所有?check<XXX>?方法調(diào)用都解釋為相應(yīng)的Permission?對(duì)象,并將它作為輸入?yún)?shù)傳遞給?AccessController?類的?checkPermission()?方法。
Java 程序中的執(zhí)行線程
在 Java 程序的執(zhí)行過(guò)程中,可能需要在不同的時(shí)間訪問(wèn)“受保護(hù)的”資源。當(dāng)我談到執(zhí)行 Java 程序時(shí),我的意思是在特定類?C?1?中(因而在特定的方法中,如?main()?)中啟動(dòng)、通過(guò)類?C?2?到?C?n-1?、并“結(jié)束”于?C?n?的執(zhí)行線程。下面是一個(gè) Java 程序執(zhí)行的典型控制流程:
調(diào)用類?C?1?的?main()?方法 ->?C?1?的?main()?方法調(diào)用?C?2?的?m?C2?方法 ->?C?2?的?m?C2?方法調(diào)用?C?3?類的?m?C3?方法 -> ... -> 類?C?n-1?的?m?Cn-1?方法調(diào)用類?C?n?的?m?Cn?方法。
假定方法?m?Cn?必須訪問(wèn)一個(gè)受保護(hù)的資源以完成其功能,它調(diào)用系統(tǒng)中生效的?AccessController?以確認(rèn)是否可以繼續(xù)請(qǐng)求的對(duì)特定“受保護(hù)的”資源的訪問(wèn)。如果?AccessController?同意放行,那么就執(zhí)行所要求的操作,控制返回給調(diào)用者(?C?n-1?類的?m?Cn-1?方法),它又將控制返回給其調(diào)用者(?C?n-2?類的?m?Cn-2?方法),如此繼續(xù)。
在 JVM 中,線程的控制流表示為?幀堆棧(stack of frame)。每個(gè)幀基本上維護(hù)有關(guān)特定?m?Ck?方法、它的類?C?k?以及這個(gè)方法調(diào)用的變量/參數(shù)的信息。圖 1 顯示了一個(gè)典型的調(diào)用堆棧。
圖 1. 典型調(diào)用堆棧的屏幕快照
上面堆棧中的每個(gè)類屬于一個(gè)?ProtectionDomain?,它由其?CodeSource?惟一地標(biāo)識(shí)。一般來(lái)說(shuō),這樣遍歷的一組?ProtectionDomain?將包含<=n個(gè)元素。(您可能還記得,一組中的每一個(gè)?ProtectionDomain?都有相關(guān)的一組權(quán)限?-- P?i?.)像圖 1 顯示的這樣一個(gè)調(diào)用堆棧快照將編寫為(codified)為一個(gè)?AccessControlContext?并由?AccessController?對(duì)象提供的本機(jī)方法調(diào)用返回。
訪問(wèn)檢查內(nèi)幕
最后得到的一組適用權(quán)限的算法是要計(jì)算所有權(quán)限的交集。換句話說(shuō),某一權(quán)限,只有與這個(gè)特定瞬間、這個(gè)線程的執(zhí)行堆棧上出現(xiàn)的所有類C?i?相應(yīng)的?ProtectionDomain?相?關(guān)聯(lián)時(shí),這個(gè)權(quán)限才適用于給定的執(zhí)行線程。
這種算法的正確性是很顯然的。通過(guò)計(jì)算與調(diào)用堆棧上所有?ProtectionDomain?s 相關(guān)聯(lián)的權(quán)限集的交集,它保證了不會(huì)因?yàn)閮蓚€(gè)類中間的一個(gè)(系統(tǒng)/應(yīng)用程序)調(diào)用另一個(gè),而使系統(tǒng)類(通常與更大的 -- 如果不是全部的 -- 一組權(quán)限相關(guān)聯(lián))“泄露”權(quán)限給應(yīng)用程序類(通常與更少的一組權(quán)限相關(guān)聯(lián))。基本上,屬于能力更低的域的類不能通過(guò)調(diào)用屬于能力更高的域的類而變得更強(qiáng)大,而屬于能力更高的域中的類會(huì)在調(diào)用能力更低的類時(shí)損失其能力。有關(guān)這種算法的形式證明請(qǐng)參閱?參考資料。
訪問(wèn)控制方法
確定權(quán)限集的交集的算法是在?AccessController?類的?checkPermission?方法中間接實(shí)現(xiàn)的。本質(zhì)上,調(diào)用這個(gè)方法所發(fā)生的事情是對(duì)那一瞬間調(diào)用堆棧和一組相互交疊的權(quán)限進(jìn)行快照。所請(qǐng)求的權(quán)限必須包含在交集結(jié)果中或者是它所隱含的。如果這種檢查判斷為?true,那么checkPermission()?方法就安靜地返回,如果不是,那么就拋出一個(gè)異常。(顯然,?圖 1中描述的調(diào)用堆棧中最后一幀實(shí)際上是對(duì)AccessController?類的?checkPermission()?方法的調(diào)用。)
注意,直到現(xiàn)在我還沒(méi)有提到圖 1 中描述的調(diào)用堆棧的線程起源。這個(gè)線程?T?2?可能是由另一個(gè)線程?T?1?在其調(diào)用堆棧中的某一點(diǎn)上創(chuàng)建的,只要 JVM 為在系統(tǒng)中執(zhí)行的每一個(gè)線程維護(hù)單獨(dú)的調(diào)用堆棧。可以直觀地假定,?T?2?將繼承?T?1?調(diào)用堆棧(不過(guò)只是?T?1?已經(jīng)運(yùn)行的那部分)以保證繼承的?ProtectionDomain?的權(quán)限集也與?T?2?自己的調(diào)用堆棧的?ProtectionDomain?取交集。這將保證子線程(這里是?T?2?)不會(huì)偷偷地得到它的父線程(在這里是?T?1?)所拒絕的某個(gè)權(quán)限。
跨域調(diào)用問(wèn)題
如果屬于能力更低的域的類調(diào)用屬于能力更高的域中的類,就有可能出現(xiàn)奇怪的現(xiàn)象。能力更高的域(類),例如?C?n?擁有可以訪問(wèn)所需要的“受保護(hù)的” 資源的權(quán)限,如果它是由沒(méi)有相關(guān)權(quán)限的、能力更低的域(類)?C?n-1?所調(diào)用的,它就不能訪問(wèn)這些資源了。如果?C?n?一定要訪問(wèn)受保護(hù)的資源才能工作怎么辦?不應(yīng)當(dāng)有這樣一種機(jī)制嗎:在確定有效的權(quán)限集,讓?C?n?可以告訴安全系統(tǒng)忽略其調(diào)用者(及調(diào)用者的調(diào)用者,并上推到調(diào)用堆棧最上層的類)的權(quán)限?
現(xiàn)在,Java 2 平臺(tái)安全體系結(jié)構(gòu)提供了一種機(jī)制,提供的就是這種功能。?AccessController?類有一個(gè)名為?doPrivileged?的方法(實(shí)際上提供了這個(gè)方法的許多變種,但是基本思路是相同的),它用特殊的旗標(biāo)標(biāo)記調(diào)用堆棧中有關(guān)的幀。在這個(gè)執(zhí)行線程中調(diào)用?checkPermission?方法時(shí),只有在這個(gè)堆棧幀?中和它下面出現(xiàn)的類的權(quán)限集才會(huì)取交集。調(diào)用類和它的上級(jí)(即所有在它?上面的堆棧幀)的權(quán)限集都?不包括在交集計(jì)算中。
不難看出為什么要包括在調(diào)用堆棧以下發(fā)生的所有類的權(quán)限集:需要考慮屬于能力更高的域的類調(diào)用屬于更能力更低的域的類的情況。更明確地說(shuō),需要防止能力更高的域 (?C?k?) 將其額外的能力傳遞給能力更低的域 (?C?k+1?)。
doPrivileged?方法的所有變種都以一個(gè)類型為?PrivilegedAction?的對(duì)象作為輸入。這個(gè)對(duì)象必須有一個(gè)名為?run()?的方法,在調(diào)用堆棧中的當(dāng)前幀特別做了如上所述的標(biāo)記時(shí),由運(yùn)行時(shí)執(zhí)行這個(gè)方法。因此,任何時(shí)候如果有一些代碼,希望在執(zhí)行時(shí)讓它的權(quán)限?臨時(shí)性地授予給調(diào)用堆棧幀前面的代碼時(shí),必須將代碼包裝為?PrivilegedAction?的形式并用這個(gè)對(duì)象作為輸入調(diào)用?AccessController?的?doPrivileged()?方法。
積極訪問(wèn)檢查與懶惰訪問(wèn)檢查
訪問(wèn)檢查算法一直被描述為計(jì)算調(diào)用堆棧上所有ProtectionDomain?s 的權(quán)限集的交集。可以用積極(eager )方式或者懶惰(lazy)方式計(jì)算這種交集。有關(guān)這兩種方法的細(xì)節(jié),請(qǐng)參閱所附的?Eager versus lazy access checks sidefile。
調(diào)用堆棧優(yōu)化
在?圖 1中看到的調(diào)用堆棧快照(或者?AccessControlContext?)是在對(duì)?AccessController進(jìn)行?checkPermission?調(diào)用時(shí)獲得的。在內(nèi)部,?AccessController?在確定這個(gè)調(diào)用堆棧時(shí)進(jìn)行一些優(yōu)化,以使訪問(wèn)檢查循環(huán)盡可能地快。這些優(yōu)化包括:
- 返回的?ProtectionDomain?只到達(dá)(并包括)通過(guò)調(diào)用?AccessController?的doPrivileged?特別標(biāo)記的第一個(gè)堆棧幀。從前面對(duì)?doPrivileged?調(diào)用的討論中顯然可以看出這樣做的原因。
- 返回的?ProtectionDomain?s 不包括系統(tǒng)域。系統(tǒng)域定義為具有所有權(quán)限,所以不需要檢查是否“隱含”了所需要的權(quán)限(它總是隱含的)。
- 返回的?ProtectionDomain?都是惟一的(即如果多個(gè)堆棧幀對(duì)應(yīng)于同一個(gè)?ProtectionDomain?,那么只會(huì)返回一個(gè)?ProtectionDomain?)。
如果搜索完當(dāng)前?AccessControlContext?并且沒(méi)有拋出?AccessControlException?,那么將對(duì)這個(gè)線程在創(chuàng)建時(shí)從其父線程“繼承”的AccessControlContext?進(jìn)行同樣的搜索(?AccessControlContext?被繼承,即一個(gè)孫子線程將繼承它的所有上級(jí)的調(diào)用堆棧)。
doPrivileged() 方法的變種
在前面看到調(diào)用?AccessController?的?doPrivileged()?方法是用一個(gè)特殊旗標(biāo)標(biāo)識(shí)調(diào)用堆棧的當(dāng)前幀,指明控制流中所有前面的幀都不進(jìn)行訪問(wèn)檢查。還看到調(diào)用堆棧快照(或者?AccessControlContext?)是在對(duì)?AccessController?進(jìn)行?checkPermission?調(diào)用時(shí)獲得的。不過(guò),這個(gè)?AccessControlContext?不一定就是應(yīng)當(dāng)用來(lái)確定是否授予所請(qǐng)求的權(quán)限的那一個(gè)?。?例如,請(qǐng)求可能是由客戶機(jī)發(fā)起并發(fā)送給服務(wù)器進(jìn)行處理。服務(wù)器通常代表客戶機(jī)執(zhí)行請(qǐng)求實(shí)施代碼。
因?yàn)榉?wù)器的一部分用于完成請(qǐng)求,如果它調(diào)用?AccessController?,那么返回的調(diào)用堆棧將是服務(wù)器的。顯然,不希望(只) 使用服務(wù)器的AccessControlContext?給客戶機(jī)授權(quán)。(當(dāng)然,希望保證服務(wù)器代碼本身對(duì)試圖訪問(wèn)的資源有相應(yīng)的權(quán)限,不過(guò)更重要的是保證客戶機(jī)對(duì)服務(wù)器代表它訪問(wèn)的資源有相應(yīng)的權(quán)限)。服務(wù)器運(yùn)行時(shí)通常是已經(jīng)授予了權(quán)限,因此,真正希望使用的是在客戶端向服務(wù)器發(fā)送請(qǐng)求時(shí)存在的客戶端調(diào)用堆棧。
AccessController?類提供了?doPrivileged()?方法的另一個(gè)變種,它以?AccessControlContext?的實(shí)例作為輸入。假定客戶機(jī)設(shè)法獲得了其AccessControlContext?的一個(gè)副本(?AccessController?類提供了實(shí)現(xiàn)這個(gè)目的的方法)并將它傳遞給服務(wù)器,服務(wù)器可以通過(guò)調(diào)用以從客戶端獲得的上述?AccessControlContext?作為輸入的?doPrivileged?,將請(qǐng)求的完成代碼作為?PrivilegedAction?執(zhí)行。
在這種情況下,權(quán)限檢查的算法(假定在過(guò)程某處,在對(duì)?PrivilegedAction?的?run()?方法調(diào)用后,調(diào)用了?checkPermission?時(shí))通過(guò)執(zhí)行上述的循環(huán)推進(jìn),直到在堆棧中遇到了特別標(biāo)記的幀,這時(shí),調(diào)用作為輸入傳遞的?AccessControlContext?對(duì)象的?checkPermission()?方法。這個(gè)調(diào)用實(shí)質(zhì)上會(huì)執(zhí)行同一個(gè)算法,但是是對(duì)于在這個(gè)?AccessControlContext?中封裝的調(diào)用堆棧(屬于客戶機(jī))執(zhí)行。
為何要使用以用戶為中心的授權(quán)?
Java 2 平臺(tái)安全體系結(jié)構(gòu)的以代碼為中心的授權(quán)基于這樣的假設(shè),即必須保護(hù)用戶不受外界影響。為了保證惡意 Java 程序(由世界上惡意破壞者編寫的)不會(huì)損壞用戶的系統(tǒng),所有移動(dòng)代碼都視為不受信任的,并且那怕進(jìn)行最無(wú)害的操作也要求具有特殊的訪問(wèn)權(quán)限。
相反,JAAS 的以用戶為中心的認(rèn)證模型是以保護(hù)世界不受用戶影響的思路開(kāi)發(fā)的。隨著越來(lái)越多的移動(dòng)和企業(yè)網(wǎng)絡(luò)的出現(xiàn),?信任概念有了不同的定義。在現(xiàn)實(shí)生活中,如果我信任某人 X 多于信任任何某人 Y,我將允許 X 有比 Y 更多的自由度。與此類似,如果一個(gè) Java 應(yīng)用程序?qū)⒂啥辔挥脩羰褂?#xff08;其中一些人實(shí)際上可能是惡意破壞者),那么最好將訪問(wèn)權(quán)限擴(kuò)展為以?每個(gè)用戶為基礎(chǔ)。在這種新模型下,根據(jù)每位用戶受信任的程度,對(duì)他或者她授權(quán)使用應(yīng)用程序的某一范圍的功能。
在下面一節(jié)中,我將重點(diǎn)介紹 Java 認(rèn)證和授權(quán)服務(wù)(Java Authentication and Authorization Service)的以用戶為中心的授權(quán)模型。雖然 JAAS 代表了 Java 平臺(tái)安全體系結(jié)構(gòu)的價(jià)值的翻天覆地的變化(即它從基于代碼的模型轉(zhuǎn)移到以用戶為基礎(chǔ)的模型),但是您會(huì)看到它的許多組件是熟悉的,盡管它們已經(jīng)更新過(guò)以滿足新的要求。
JAAS 授權(quán)體系結(jié)構(gòu)
JAAS 最初是作為 JDK 的一個(gè)擴(kuò)展引入的,在版本 1.4 時(shí)成為了核心 JDK 的一部分。既然 JAAS 的目的是為了以每位用戶為基礎(chǔ)控制任何一段代碼所能做的事情,因此需要首先能夠準(zhǔn)確和惟一地標(biāo)識(shí)用戶,換句話說(shuō),必須能夠?qū)λ麄冞M(jìn)行認(rèn)證。雖然在這里我不會(huì)在 JAAS 的“認(rèn)證”方面花很多時(shí)間(有關(guān)這個(gè)主題的更多參考請(qǐng)參閱?參考資料),但是我將重點(diǎn)介紹它的一個(gè)核心組件:?Subject?類。
就像以前一直說(shuō)的,JAAS 是一種用以用戶為中心進(jìn)行授權(quán)的方式。在 JAAS 下,相關(guān)的問(wèn)題不再是(像在 Java 2 平臺(tái)安全體系結(jié)構(gòu)中那樣)“哪些是這段代碼可以做的?”,而變?yōu)椤斑@個(gè)認(rèn)證用戶的訪問(wèn)權(quán)限是什么?”因此,在本文的其余部分我將著重介紹 JAAS 中?Subject?類的作用,并深入討論基于 subject 的訪問(wèn)控制。
基于 subject 的訪問(wèn)控制
Subject?類用于表示在給定系統(tǒng)中認(rèn)證的用戶(即填充的?Subject?是 JAAS 認(rèn)證過(guò)程的結(jié)果)。在內(nèi)部,?Subject?包含一組?Principal?對(duì)象(和其他有關(guān)用戶的信息),其中每個(gè)?Principal?對(duì)象表示同一個(gè)用戶的不同“身份”。例如,一個(gè)?Principal?可能是我在一個(gè)終端系統(tǒng)上的用戶 ID,而另一個(gè)可能是我在同一系統(tǒng)上所屬于的“組”。
在前面我介紹過(guò)?生效的 Policy?是如何在系統(tǒng)中設(shè)置?ProtectionDomain?(以及由相關(guān)的?CodeSource?標(biāo)識(shí)的、“屬于”它的類)和授予它的權(quán)限之間的映射的。JAAS 通過(guò)要求用一組?Principal?進(jìn)一步描述?ProtectionDomain?(超越了?CodeSource?)而強(qiáng)化了這種概念。當(dāng)系統(tǒng)?Policy設(shè)置了這樣的?ProtectionDomain?(即除了?CodeSource?,還用一組?Principal?s 描述)和授予它的權(quán)限之間的映射后,如果要用ProtectionDomain?的權(quán)限檢查是否應(yīng)當(dāng)授予用戶某個(gè)請(qǐng)求的權(quán)限,那么在?Subject?中包含的、與運(yùn)行這段代碼的認(rèn)證用戶相對(duì)應(yīng)的Principal?對(duì)象必須匹配在這個(gè)?ProtectionDomain?中包含的?Principal?對(duì)象。
既然 Java 2 平臺(tái)已經(jīng)有了干凈的、高效的、使用調(diào)用堆棧(通過(guò)?AccessControlContext?)的授權(quán)實(shí)現(xiàn),那么保持它就容易得多了,只要提供一種機(jī)制將運(yùn)行這段代碼的用戶的身份(如由用戶的?Subject?所提供的)“注入”到在權(quán)限檢查瞬間調(diào)用堆棧中的?ProtectionDomain?。
為此,JAAS?Subject?類提供了兩個(gè)靜態(tài)方法,稱為?doAs?和?doAsPrivileged?。 這些方法期待的輸入是認(rèn)證的用戶的?Subject?實(shí)例和PrivilegedAction?的一個(gè)實(shí)例(它的?run()?方法應(yīng)當(dāng)包含需要訪問(wèn)受保護(hù)的資源的業(yè)務(wù)邏輯)。基本思路是應(yīng)用程序應(yīng)當(dāng)首先認(rèn)證用戶,對(duì)認(rèn)證的用戶建立了?Subject?后,這個(gè)用戶可能希望執(zhí)行的每一個(gè)操作都包裝為?PrivilegedAction?、并由應(yīng)用程序作為?Subject?(就像方法自己的名字所表明的 --?doAs()?!)執(zhí)行。這兩個(gè)方法之間有細(xì)微但是重要的區(qū)別,我們將在稍后介紹。
為了能夠?qū)⒉僮髯鳛?Subject?執(zhí)行,必須在調(diào)用堆?棧中將 Subject引入(或者注入)ProtectionDomain?。這是在一個(gè)名為?DomainCombiner的專用接口的幫助下實(shí)現(xiàn)的,我將在開(kāi)始?doAs()?和?doAsPrivileged()?方法的內(nèi)幕之前介紹這個(gè)接口。
DomainCombiner
如前所述,對(duì)于一個(gè)?AccessControlContext?(一個(gè)調(diào)用堆棧),在 JAAS 中將?Subject?注入堆棧中的?ProtectionDomain?是通過(guò)實(shí)現(xiàn)DomainCombiner?接口(一個(gè)特定的實(shí)現(xiàn)是?SubjectDomainCombiner?)所處理的。
注入是在將?SubjectDomainCombiner?作為構(gòu)造函數(shù)參數(shù)傳遞以構(gòu)建?AccessControlContext?時(shí)執(zhí)行的。(作為參數(shù)傳遞給?doAs?調(diào)用的Subject?被封裝到?SubjectDomainCombiner?對(duì)象中,這種封裝是在創(chuàng)建后者時(shí),將?Subject?作為構(gòu)造函數(shù)參數(shù)傳遞而完成的。)不過(guò),真正的工作是在?SubjectDomainCombiner?的?combine()?方法中完成的。您將在稍后看到在這個(gè)方法中所發(fā)生的過(guò)程。
Subject.doAs() 方法
應(yīng)用程序可能期待在認(rèn)證用戶之后調(diào)用?Subject.doAs()?方法(即,當(dāng)?Subject?對(duì)用戶是可用的時(shí))。在內(nèi)部,這個(gè)調(diào)用會(huì)產(chǎn)生下列活動(dòng):
調(diào)用?Subject.doAs()?方法的另一個(gè)效果是:可以通過(guò)?PrivilegedAction?的?run()?方法達(dá)到的任何代碼都可以使用認(rèn)證用戶的身份(即Subject?)。得到?Subject?的方法如下:
這樣返回的?Subject?表明了認(rèn)證用戶的身份,可以用于登錄和/或數(shù)據(jù)級(jí)的授權(quán)等。
Subject.doAsPrivileged() 方法
像在?doAs()?方法中看到的那樣,在調(diào)用?doAs?之前,請(qǐng)求的?Permission?必須由出現(xiàn)在調(diào)用堆棧中的?ProtectionDomain?s 所隱含。由于現(xiàn)在已經(jīng)熟悉的原因,可能不總是希望是這種情況。
正如在討論?AccessController?類的?doPrivileged()?方法(這個(gè)方法以一個(gè)?AccessControlContext?為參數(shù)用于權(quán)限檢查)的變種時(shí)提到的,PrivilegedAction?可能實(shí)際上表示一些服務(wù)器代表客戶機(jī)執(zhí)行的一些操作(更準(zhǔn)確地說(shuō)是作為客戶機(jī),即好像假定服務(wù)器具有它代表其執(zhí)行操作的客戶機(jī)的身份)。在這種情況下,在調(diào)用?doAs?之前調(diào)用堆棧的快照將包含服務(wù)器的內(nèi)部代碼的?ProtectionDomain?,而讓這些?ProtectionDomain必須隱含一個(gè)任意請(qǐng)求的?Permission?顯然沒(méi)有意義。然而,所希望的是以下兩種情況之一:
- 第 I 種情況: 應(yīng)當(dāng)用在客戶端調(diào)用堆棧上的?ProtectionDomains?(當(dāng)客戶機(jī)向服務(wù)器發(fā)送請(qǐng)求的瞬間的快照)檢查請(qǐng)求的?Permission?(以及與用戶身份相關(guān)聯(lián)的服務(wù)器端調(diào)用堆?棧ProtectionDomain?)。
- 第 II 種情況:應(yīng)當(dāng)只用與用戶身份相關(guān)聯(lián)的服務(wù)器端調(diào)用堆棧?ProtectionDomain?進(jìn)行權(quán)限檢查。
這個(gè)工具是通過(guò)?Subject?類的 static?doAsPrivileged()?方法提供的。這個(gè)方法以一個(gè)?Subject?和一個(gè)?PrivilegedAction?作為輸入?yún)?shù)(就像?doAs()?方法),不過(guò),它還有一個(gè)?AccessControlContext?參數(shù)。這樣,客戶機(jī)可以安排取它自己的?AccessControlContext?快照并發(fā)送給服務(wù)器,這樣就可以將它傳遞?給 doAsPrivileged?調(diào)用。這樣可以處理上面第 I 種情況。否則,可以傳遞 null 代替AccessControlContext?調(diào)用?doAsPrivileged?,這樣可以處理上述第 II 種情況。
在內(nèi)部,?doAsPrivileged()?方法的步驟如下:
授權(quán)模型的矛盾
我在這篇導(dǎo)游中討論了 Java 授權(quán)內(nèi)幕的大量基礎(chǔ)內(nèi)容。介紹了原來(lái) Java 2 平臺(tái)安全體系結(jié)構(gòu)的基于代碼的授權(quán)模型和在 JAAS 中引入的基于用戶的授權(quán)框架。在本導(dǎo)游的最后一程,將介紹 JAAS 認(rèn)證模型中的一個(gè)矛盾,并且我將描述一個(gè)解決它的實(shí)際方法。
嗨,我的 Subject 到哪里去了?
假設(shè)應(yīng)用程序認(rèn)證了用戶并為她設(shè)置了一個(gè)?Subject?。用戶請(qǐng)求某個(gè)功能,于是應(yīng)用程序調(diào)用?doAsPrivileged()?方法并傳遞認(rèn)證的?Subject?和結(jié)合了所需要功能的?PrivilegedAction?。傳遞的?AccessControlContext?為 null,保證只對(duì)調(diào)用堆棧中調(diào)用?doAsPrivileged?之后的ProtectionDomain?進(jìn)行權(quán)限檢查。
考慮執(zhí)行?PrivilegedAction?實(shí)例的?run()?方法。可以從前面看到,在這個(gè)?PrivilegedAction?中的一段代碼應(yīng)當(dāng)可以請(qǐng)求并得到認(rèn)證的Subject?。現(xiàn)在假定在這個(gè)方法中的控制流中某個(gè)地方,調(diào)用了?AccessController?的?doPrivileged()?方法(特別是只接受PrivilegedAction實(shí)例的doPrivileged?)和在這個(gè)(嵌入的)?doPrivileged?調(diào)用中執(zhí)行的?PrivilegedAction?也需要提到認(rèn)證用戶的身份。
與以前一樣,第一步是通過(guò)調(diào)用?AccessController?的?getContext()?方法得到當(dāng)前?AccessControlContext?的句柄。如在前面討論Subject.doAs()?方法時(shí)所說(shuō),與當(dāng)前調(diào)用堆棧一同返回的還有一個(gè)?privilegedAccessControlContext?(包含封裝了認(rèn)證的?Subject?的SubjectDomainCombiner?),所以優(yōu)化過(guò)程可以實(shí)際上將一組?Principal?從?Subject?注入到最后一?組 ProtectionDomain?列表中。不過(guò),因?yàn)閷?duì)?AccessController?的?doPrivileged()?方法進(jìn)行了新的調(diào)用,分配了一個(gè)新的?privileged 元素,和用這個(gè)元素更新的當(dāng)前執(zhí)行線程作為最高層的 privileged 元素。因?yàn)闆](méi)有向?doPrivileged?調(diào)用傳遞?AccessControlContext?,所以這個(gè) privileged 元素沒(méi)有任何 privileged?AccessControlContext?與之相關(guān)聯(lián),這與前面提到的情況不一樣。對(duì)?getContext?的調(diào)用返回直到這個(gè)最高 privileged 元素的調(diào)用堆棧,因此,有關(guān)認(rèn)證的?Subject?信息在這個(gè)執(zhí)行期間是不可用的。
當(dāng)然,一旦?innerPrivilegedAction?執(zhí)行完,這個(gè) privileged 元素就彈出堆棧,而對(duì)?getContext?的所有調(diào)用都會(huì)再返回包含 privilegedAccessControlContext?的?AccessControlContext?(它又包含封裝了認(rèn)證?Subject?的?SubjectDomainCombiner?)。因此,當(dāng)從Subject.doAs()?方法中調(diào)用的?PrivilegedAction?完成后,將可以再次獲得?認(rèn)證的Subject?。
實(shí)用解決
方法?
一種解決這個(gè)問(wèn)題的方法是創(chuàng)建一個(gè)自定義?SubjectHolder?類,它包裝了一個(gè) static?ThreadLocal?以存儲(chǔ)當(dāng)前?Subject?。?認(rèn)證的Subject可以在認(rèn)證之后和調(diào)用?doAs()?方法之前存儲(chǔ)在這個(gè)?SubjectHolder?中。這之后,所有執(zhí)行的代碼(直接或者間接,不管是否包裝在另一個(gè)PrivilegedAction?中)都將可以得到認(rèn)證的?Subject?,只要讓?SubjectHolder?返回?ThreadLocal?變量的內(nèi)容。
WebSphere 應(yīng)用服務(wù)器提供了一個(gè)這種解決方法的例子。該應(yīng)用服務(wù)器提供了一個(gè)幫助器類?WSSubject?,它有 static?doAs()?和doAsPrivileged()?方法,它們具有相同的?Subject?類簽名。在調(diào)用相應(yīng)的?Subject.doAs()?方法之前,?WSSubject.doAs()?方法基本上將用戶憑據(jù)與當(dāng)前執(zhí)行線程(可以用于 Enterprise JavaBean (EJB)調(diào)用)相關(guān)聯(lián)。在離開(kāi)?WSSubject.doAs()?方法時(shí),恢復(fù)原來(lái)的憑據(jù)并與執(zhí)行線程相關(guān)聯(lián)。
結(jié)束語(yǔ)
本文深入分析了 Java 2 平臺(tái)安全體系結(jié)構(gòu)和 JAAS 的 Java 授權(quán)。完成本文(或者游覽)后,應(yīng)當(dāng)可以對(duì)每一種授權(quán)框架的基礎(chǔ)概念及它們的底層機(jī)制有全面的了解。
Java 2 平臺(tái)安全體系結(jié)構(gòu)和 JAAS 共同構(gòu)成了當(dāng)前的 Java 授權(quán)模型。我介紹了 JAAS 授權(quán)模型中的一個(gè)矛盾之處,并描述了一種解決它的實(shí)用方法,并提供了它的現(xiàn)實(shí)世界實(shí)現(xiàn)的一個(gè)例子。
轉(zhuǎn)自:http://www.ibm.com/developerworks/cn/java/j-javaauth/
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/p/3801642.html
總結(jié)
以上是生活随笔為你收集整理的Java 授权内幕--转载的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: JAVA 上加密算法的实现用例---转载
- 下一篇: Java 理论与实践: 非阻塞算法简介-