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