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