基于 Java 2 运行时安全模型的线程协作--转
在 Java 2 之前的版本,運行時的安全模型使用非常嚴格受限的沙箱模型(Sandbox)。讀者應該熟悉,Java 不受信的 Applet 代碼就是基于這個嚴格受限的沙箱模型來提供運行時的安全檢查。沙箱模型的本質是,任何本地運行的代碼都是受信的,有完全的權限來存取關鍵的系統資源。而對于 Applet,則屬于不受信的代碼,只能訪問沙箱范圍內有限的資源。當然,您可以通過數字簽名的方式配置您的 Applet 為受信的代碼,具有同本地代碼一樣的權限。
從 Java 2 開始,Java 提供了基于策略(Policy)與堆棧授權的運行時安全模型,它是一個更加細粒度的存取控制,易于配置與擴展,其總體的架構如圖 1 所示:
圖 1. Java 2 安全模型
簡單來講,當類由類裝載器(Class Loader)載入到 JVM 運行,這些運行時的類會根據 Java Policy 文件的配置,被賦予不同的權限。當這些類要訪問某些系統資源(例如打開 Socket、讀寫文件等),或者執行某些敏感的操作(例如存取密碼)時,Java 的安全管理器(ava.lang.SecuirtyManager)的檢查權限方法將被調用,檢查這些類是否具有必要的權限來執行該操作。
在繼續深入討論之前,我們先來澄清下面的幾個概念:
- 策略,即系統安全策略,由用戶或者管理員配置,用來配置執行代碼的權限。運行時的 java.security.Policy 對象用來代表該策略文件。
- 權限,Java 定義了層次結構的權限對象,所有權限對象的根類是 java.security.Permission。權限的定義涉及兩個核心屬性:目標(Target)與動作 (Action)。例如對于文件相關的權限定義,其目標就是文件或者目錄,其動作包括:讀,寫,刪除等。
- 保護域,保護域可以理解為具有共同的權限集的類的集合。
在 Java 2 里,權限實際上是被賦予保護域的,而不是直接賦給類。權限、保護域和類之間的映射關系如圖 2。
圖 2. 類,保護域,權限的映射關系
如圖 2 所示,當前運行時堆棧是從 a.class 到 e.class。在運行時堆棧上的每一幀(Stack Frame)都會被 Java 劃歸為某個保護域(保護域是 Java 根據 Policy 文件配置構建出來的)。Java 的安全管理器在執行權限檢查時,會對堆棧上的每個 Stack Frame 做權限檢查,當且僅當每個 Stack Frame 被賦予的權限集都暗含(Imply)了所要求的權限時,該操作才被允許執行,否則 java.security.AccessControlException 異常將被拋出,該操作執行失敗。
有關 Java 2 安全模型,有幾點需要特別說明:
圖 3. doPrivileged Stack Frame
接下來,本文會給出一個簡單的示例,然后我們根據這個示例,進一步深入,來創建一個線程間安全協作的應用。
示例
我們的示例很簡單:客戶端調用 LogService 提供的 API,把 Message 寫入到磁盤文件。
清單 1. 客戶端程序
package sample.permtest.client;……public class Client {……public static void main(String[] args) {//構造消息日志,使用LogService將其寫入c:\\paper\\client\\out.tmp文件。Message message = new Message("c:\\paper\\client\\out.tmp", "Hi, this is called from client"+'\n');LogService.instance.log(message);//構造消息日志,使用LogService將其寫入c:\\paper\\server\\out.tmp文件。message = new Message("c:\\paper\\server\\out.tmp", "Hi, this is called from client"+'\n');LogService.instance.log(message); } }清單 2. LogService
package sample.permtest.server; …… public class LogService {……public void log(Message message) {final String destination = message.getDestination();final String info = message.getInfo();FileWriter filewriter = null;try{filewriter = new FileWriter(destination, true);filewriter.write(info);filewriter.close();}catch (IOException ioexception){ioexception.printStackTrace();}} }如清單 1、2 所示,這就是一個普通的 Java 應用程序。我們把這個程序放在 Java 的安全模型中執行。Client 類放在 client.jar JAR 包里,而 LogService 類放在 server.jar JAR 包里
首先我們使用 keytool 工具來生成我們需要的 keystore 文件,以及需要的數字證書,如清單 3 所示。
清單 3. 生成 keystore 文件及其數字證書
>keytool -genkey -alias client -keyalg RSA -keystore C:\paper\.keystore >keytool -genkey -alias server -keyalg RSA -keystore C:\paper\.keystore在清單 3 中,我們生成了 C:\paper\.keystore 文件,使用 RSA 算法生成了別名為 client 與 server 的兩個數字證書。(注 : 為方便起見,keystore 與 client,server 證書的密鑰都是 111111)
我們使用如清單 4 所示的命令來簽名 client.jar 與 server.jar。
清單 4. 簽名 JAR 文件
>jarsigner.exe -keystore C:\paper\.keystore -storepass 111111 c:\paper\client.jar client >jarsigner.exe -keystore C:\paper\.keystore -storepass 111111 c:\paper\server.jar server在清單 4 中,我們使用了別名為 client 的數字證書來簽名 client.jar 文件,使用別名為 server 的數字證書來簽名 server.jar 文件。
使用圖形化的工具 policytool.exe 創建清單 5 所示的 Policy 文件。
清單 5. Policy 文件
/* AUTOMATICALLY GENERATED ON Thu May 14 15:40:25 CST 2009*/ /* DO NOT EDIT */ keystore "file:C:/paper/.keystore"; grant signedBy "client" { permission java.io.FilePermission "c:\\paper\\client\\*","read,write"; }; grant signedBy "server" { permission java.security.AllPermission; };Policy 文件指出,所有被”client”簽名的代碼具有讀寫” c:\\paper\\client\\”目錄下所有文件的權限,而所有被”server”簽名的代碼具有所有的權限。Java 將根據該策略文件按照簽名者創建相應的保護域。
一切就緒,我們運行代碼,如清單 6 所示。
清單 6. 運行程序
>java -Djava.security.manager -Djava.security.policy=my.policy -classpath client.jar;server.jar sample.permtest.client.Client有兩個運行時選項特別重要,-Djava.security.manager 告訴 JVM 裝載 Java 的安全管理器,進行運行時的安全檢查,而 -Djava.security.policy 用來指定我們使用的策略文件。
運行的結果如清單 7 所示。
清單 7. 運行結果
Exception in thread "main" java.security.AccessControlException: access denied ( java.io.FilePermission c:\paper\server\out.tmp write) at java.security.AccessControlContext.checkPermission(Unknown Source) at java.security.AccessController.checkPermission(Unknown Source) at java.lang.SecurityManager.checkPermission(Unknown Source) at java.lang.SecurityManager.checkWrite(Unknown Source) at java.io.FileOutputStream.<init>(Unknown Source) at java.io.FileOutputStream.<init>(Unknown Source) at java.io.FileWriter.<init>(Unknown Source) at sample.permtest.server.LogService.log(LogService.java:19) at sample.permtest.client.Client.main(Client.java:16)客戶端運行后,第一條消息成功寫入 c:\\paper\\client\\out.tmp 文件,而第二條消息由于沒有 c:\paper\server\out.tmp 文件的寫權限而被拒絕執行。
回頁首
線程間的安全協作
前一節本文給出的示例,如果放在線程間異步協作的環境里,情況會變得復雜。如圖 4 所示。
圖 4. 線程的異步協作
如圖 4,在這樣的情景下,客戶端線程的運行時堆棧完全獨立于服務器端的線程,它們之間僅僅通過共享的數據結構消息隊列進行異步協作。例如:當客戶端線程放入 Message X,而后,服務器端的線程拿到 Message X 進行處理,我們仍然假設 Message X 是希望服務器端線程將消息寫入 c:\paper\server\out.tmp 文件。在這個時候,服務程序怎樣才能確保客戶端具有寫入 c:\paper\server\out.tmp 文件的權限?
Java 提供了基于線程協作場景的解決方案,如清單 8 所示:
清單 8. 線程協作版本的 LogService
package sample.permtest.thread.server; …… public class LogService implements Runnable {……public synchronized void log(Message message){//該方法將在客戶端線程環境中執行//在消息放入隊列的時候,我們把客戶端線程的執行環境通過 //AccessController.getContext() 得到,//并及時保存下來。message.m_accesscontrolcontext = AccessController.getContext();_messageList.add(message);notifyAll();}……//從隊列中取出消息,并逐一處理public void run(){while (true){Message message = null;try{message = retrieveMessage();}catch (InterruptedException interruptedexception){break;}final String destination = message.getDestination();final String stringMessage = message.getInfo();AccessController.doPrivileged(new PrivilegedAction(){public Object run(){FileWriter filewriter = null;try{filewriter = new FileWriter(destination, true);filewriter.write(stringMessage);filewriter.close();}catch (IOException ioexception){ioexception.printStackTrace();}return null;}},message.m_accesscontrolcontext //將客戶端線程當時的執行環境傳入,進行權限檢查。);}} }消息類的 m_accesscontrolcontext 成員變量是一個 AccessControlContext 對象,它封裝的當前線程的執行環境快照,我們可以通過調用 AccessController 的 getContext 方法獲得。安全的線程協作工作原理如圖 5 所示。
圖 5. 線程異步協作權限檢查路徑
圖 5 中的箭頭指示了 Java 的安全管理器權限檢查的路徑,從當前的幀 (Frame) 開始,沿著服務器端線程的運行時堆棧檢查,直到碰到了 AccessController.doPrivileged 幀。由于我們在調用 doPrivileged 方法時,傳入了 m_accesscontrolcontext,也就是客戶端線線程在往消息隊列里插入消息時的執行環境,所以 Java 的安全管理器會跳轉到該執行環境,沿著客戶端插入消息時的執行堆棧逐一檢查。
在本節線程版本的 Log 服務實現中,Client 類在 sample.permtest.thread.client 包里,該包被導出為 thread_client.jar JAR 包,而 LogService 在 sample.permtest.thread.server 包里,該包被導出為 thread_server.jar JAR 包。而有關這部分的包簽名與上節類似,使用了與上節相同的數字證書。
關于完整的源代碼,讀者可以在本文后面的資源列表中下載。
回頁首
小結
本文通過示例,詳盡描述了 Java 2 運行時的安全模型特性,以及基于該模型,如何構建安全的線程協作應用。值得一提的是,當您的 Java 應用使用的 Java 2 所提供的運行時安全模型,程序性能的降低是必然的,因為我們已經看到,Java 2 的安全模型是基于堆棧授權的,這意味著,每一次 Java 安全管理器檢查權限方法的執行,都會遍歷當前運行時行堆棧的所有幀,以確定是否滿足權限要求。所以您的設計一定要在安全與性能之間取舍。當然,當您在應用了 Java 的安全模型后,您仍然有機會進行性能的優化,比如使用 doPrivileged 方法去掉不必要的堆棧遍歷,更進一步,您可以根據自己應用的特點,通過繼承 java.lang. SecurityManager 類,來開發適合自己應用的安全管理器。
原文地址:http://www.ibm.com/developerworks/cn/java/j-lo-rtsecurity/
附錄,java權限控制代碼:
java.security.AccessController.doPrivileged(new java.security.PrivilegedExceptionAction<Void>() {@Overridepublic Void run() throws ServletException, IOException {internalDoFilter(req,res);return null;}});?
轉載于:https://www.cnblogs.com/davidwang456/p/4048916.html
總結
以上是生活随笔為你收集整理的基于 Java 2 运行时安全模型的线程协作--转的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring AOP 实现原理与 CGL
- 下一篇: 深入探讨 java.lang.ref 包