基于 Java 2 运行时安全模型的线程协作--转
在 Java 2 之前的版本,運(yùn)行時(shí)的安全模型使用非常嚴(yán)格受限的沙箱模型(Sandbox)。讀者應(yīng)該熟悉,Java 不受信的 Applet 代碼就是基于這個(gè)嚴(yán)格受限的沙箱模型來提供運(yùn)行時(shí)的安全檢查。沙箱模型的本質(zhì)是,任何本地運(yùn)行的代碼都是受信的,有完全的權(quán)限來存取關(guān)鍵的系統(tǒng)資源。而對(duì)于 Applet,則屬于不受信的代碼,只能訪問沙箱范圍內(nèi)有限的資源。當(dāng)然,您可以通過數(shù)字簽名的方式配置您的 Applet 為受信的代碼,具有同本地代碼一樣的權(quán)限。
從 Java 2 開始,Java 提供了基于策略(Policy)與堆棧授權(quán)的運(yùn)行時(shí)安全模型,它是一個(gè)更加細(xì)粒度的存取控制,易于配置與擴(kuò)展,其總體的架構(gòu)如圖 1 所示:
圖 1. Java 2 安全模型
簡單來講,當(dāng)類由類裝載器(Class Loader)載入到 JVM 運(yùn)行,這些運(yùn)行時(shí)的類會(huì)根據(jù) Java Policy 文件的配置,被賦予不同的權(quán)限。當(dāng)這些類要訪問某些系統(tǒng)資源(例如打開 Socket、讀寫文件等),或者執(zhí)行某些敏感的操作(例如存取密碼)時(shí),Java 的安全管理器(ava.lang.SecuirtyManager)的檢查權(quán)限方法將被調(diào)用,檢查這些類是否具有必要的權(quán)限來執(zhí)行該操作。
在繼續(xù)深入討論之前,我們先來澄清下面的幾個(gè)概念:
- 策略,即系統(tǒng)安全策略,由用戶或者管理員配置,用來配置執(zhí)行代碼的權(quán)限。運(yùn)行時(shí)的 java.security.Policy 對(duì)象用來代表該策略文件。
- 權(quán)限,Java 定義了層次結(jié)構(gòu)的權(quán)限對(duì)象,所有權(quán)限對(duì)象的根類是 java.security.Permission。權(quán)限的定義涉及兩個(gè)核心屬性:目標(biāo)(Target)與動(dòng)作 (Action)。例如對(duì)于文件相關(guān)的權(quán)限定義,其目標(biāo)就是文件或者目錄,其動(dòng)作包括:讀,寫,刪除等。
- 保護(hù)域,保護(hù)域可以理解為具有共同的權(quán)限集的類的集合。
在 Java 2 里,權(quán)限實(shí)際上是被賦予保護(hù)域的,而不是直接賦給類。權(quán)限、保護(hù)域和類之間的映射關(guān)系如圖 2。
圖 2. 類,保護(hù)域,權(quán)限的映射關(guān)系
如圖 2 所示,當(dāng)前運(yùn)行時(shí)堆棧是從 a.class 到 e.class。在運(yùn)行時(shí)堆棧上的每一幀(Stack Frame)都會(huì)被 Java 劃歸為某個(gè)保護(hù)域(保護(hù)域是 Java 根據(jù) Policy 文件配置構(gòu)建出來的)。Java 的安全管理器在執(zhí)行權(quán)限檢查時(shí),會(huì)對(duì)堆棧上的每個(gè) Stack Frame 做權(quán)限檢查,當(dāng)且僅當(dāng)每個(gè) Stack Frame 被賦予的權(quán)限集都暗含(Imply)了所要求的權(quán)限時(shí),該操作才被允許執(zhí)行,否則 java.security.AccessControlException 異常將被拋出,該操作執(zhí)行失敗。
有關(guān) Java 2 安全模型,有幾點(diǎn)需要特別說明:
圖 3. doPrivileged Stack Frame
接下來,本文會(huì)給出一個(gè)簡單的示例,然后我們根據(jù)這個(gè)示例,進(jìn)一步深入,來創(chuàng)建一個(gè)線程間安全協(xié)作的應(yīng)用。
示例
我們的示例很簡單:客戶端調(diào)用 LogService 提供的 API,把 Message 寫入到磁盤文件。
清單 1. 客戶端程序
package sample.permtest.client;……public class Client {……public static void main(String[] args) {//構(gòu)造消息日志,使用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);//構(gòu)造消息日志,使用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 所示,這就是一個(gè)普通的 Java 應(yīng)用程序。我們把這個(gè)程序放在 Java 的安全模型中執(zhí)行。Client 類放在 client.jar JAR 包里,而 LogService 類放在 server.jar JAR 包里
首先我們使用 keytool 工具來生成我們需要的 keystore 文件,以及需要的數(shù)字證書,如清單 3 所示。
清單 3. 生成 keystore 文件及其數(shù)字證書
>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 的兩個(gè)數(shù)字證書。(注 : 為方便起見,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 的數(shù)字證書來簽名 client.jar 文件,使用別名為 server 的數(shù)字證書來簽名 server.jar 文件。
使用圖形化的工具 policytool.exe 創(chuàng)建清單 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\\”目錄下所有文件的權(quán)限,而所有被”server”簽名的代碼具有所有的權(quán)限。Java 將根據(jù)該策略文件按照簽名者創(chuàng)建相應(yīng)的保護(hù)域。
一切就緒,我們運(yùn)行代碼,如清單 6 所示。
清單 6. 運(yùn)行程序
>java -Djava.security.manager -Djava.security.policy=my.policy -classpath client.jar;server.jar sample.permtest.client.Client有兩個(gè)運(yùn)行時(shí)選項(xiàng)特別重要,-Djava.security.manager 告訴 JVM 裝載 Java 的安全管理器,進(jìn)行運(yùn)行時(shí)的安全檢查,而 -Djava.security.policy 用來指定我們使用的策略文件。
運(yùn)行的結(jié)果如清單 7 所示。
清單 7. 運(yùn)行結(jié)果
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)客戶端運(yùn)行后,第一條消息成功寫入 c:\\paper\\client\\out.tmp 文件,而第二條消息由于沒有 c:\paper\server\out.tmp 文件的寫權(quán)限而被拒絕執(zhí)行。
回頁首
線程間的安全協(xié)作
前一節(jié)本文給出的示例,如果放在線程間異步協(xié)作的環(huán)境里,情況會(huì)變得復(fù)雜。如圖 4 所示。
圖 4. 線程的異步協(xié)作
如圖 4,在這樣的情景下,客戶端線程的運(yùn)行時(shí)堆棧完全獨(dú)立于服務(wù)器端的線程,它們之間僅僅通過共享的數(shù)據(jù)結(jié)構(gòu)消息隊(duì)列進(jìn)行異步協(xié)作。例如:當(dāng)客戶端線程放入 Message X,而后,服務(wù)器端的線程拿到 Message X 進(jìn)行處理,我們?nèi)匀患僭O(shè) Message X 是希望服務(wù)器端線程將消息寫入 c:\paper\server\out.tmp 文件。在這個(gè)時(shí)候,服務(wù)程序怎樣才能確??蛻舳司哂袑懭?c:\paper\server\out.tmp 文件的權(quán)限?
Java 提供了基于線程協(xié)作場景的解決方案,如清單 8 所示:
清單 8. 線程協(xié)作版本的 LogService
package sample.permtest.thread.server; …… public class LogService implements Runnable {……public synchronized void log(Message message){//該方法將在客戶端線程環(huán)境中執(zhí)行//在消息放入隊(duì)列的時(shí)候,我們把客戶端線程的執(zhí)行環(huán)境通過 //AccessController.getContext() 得到,//并及時(shí)保存下來。message.m_accesscontrolcontext = AccessController.getContext();_messageList.add(message);notifyAll();}……//從隊(duì)列中取出消息,并逐一處理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 //將客戶端線程當(dāng)時(shí)的執(zhí)行環(huán)境傳入,進(jìn)行權(quán)限檢查。);}} }消息類的 m_accesscontrolcontext 成員變量是一個(gè) AccessControlContext 對(duì)象,它封裝的當(dāng)前線程的執(zhí)行環(huán)境快照,我們可以通過調(diào)用 AccessController 的 getContext 方法獲得。安全的線程協(xié)作工作原理如圖 5 所示。
圖 5. 線程異步協(xié)作權(quán)限檢查路徑
圖 5 中的箭頭指示了 Java 的安全管理器權(quán)限檢查的路徑,從當(dāng)前的幀 (Frame) 開始,沿著服務(wù)器端線程的運(yùn)行時(shí)堆棧檢查,直到碰到了 AccessController.doPrivileged 幀。由于我們?cè)谡{(diào)用 doPrivileged 方法時(shí),傳入了 m_accesscontrolcontext,也就是客戶端線線程在往消息隊(duì)列里插入消息時(shí)的執(zhí)行環(huán)境,所以 Java 的安全管理器會(huì)跳轉(zhuǎn)到該執(zhí)行環(huán)境,沿著客戶端插入消息時(shí)的執(zhí)行堆棧逐一檢查。
在本節(jié)線程版本的 Log 服務(wù)實(shí)現(xiàn)中,Client 類在 sample.permtest.thread.client 包里,該包被導(dǎo)出為 thread_client.jar JAR 包,而 LogService 在 sample.permtest.thread.server 包里,該包被導(dǎo)出為 thread_server.jar JAR 包。而有關(guān)這部分的包簽名與上節(jié)類似,使用了與上節(jié)相同的數(shù)字證書。
關(guān)于完整的源代碼,讀者可以在本文后面的資源列表中下載。
回頁首
小結(jié)
本文通過示例,詳盡描述了 Java 2 運(yùn)行時(shí)的安全模型特性,以及基于該模型,如何構(gòu)建安全的線程協(xié)作應(yīng)用。值得一提的是,當(dāng)您的 Java 應(yīng)用使用的 Java 2 所提供的運(yùn)行時(shí)安全模型,程序性能的降低是必然的,因?yàn)槲覀円呀?jīng)看到,Java 2 的安全模型是基于堆棧授權(quán)的,這意味著,每一次 Java 安全管理器檢查權(quán)限方法的執(zhí)行,都會(huì)遍歷當(dāng)前運(yùn)行時(shí)行堆棧的所有幀,以確定是否滿足權(quán)限要求。所以您的設(shè)計(jì)一定要在安全與性能之間取舍。當(dāng)然,當(dāng)您在應(yīng)用了 Java 的安全模型后,您仍然有機(jī)會(huì)進(jìn)行性能的優(yōu)化,比如使用 doPrivileged 方法去掉不必要的堆棧遍歷,更進(jìn)一步,您可以根據(jù)自己應(yīng)用的特點(diǎn),通過繼承 java.lang. SecurityManager 類,來開發(fā)適合自己應(yīng)用的安全管理器。
原文地址:http://www.ibm.com/developerworks/cn/java/j-lo-rtsecurity/
附錄,java權(quán)限控制代碼:
java.security.AccessController.doPrivileged(new java.security.PrivilegedExceptionAction<Void>() {@Overridepublic Void run() throws ServletException, IOException {internalDoFilter(req,res);return null;}});?
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/p/4048916.html
總結(jié)
以上是生活随笔為你收集整理的基于 Java 2 运行时安全模型的线程协作--转的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring AOP 实现原理与 CGL
- 下一篇: 深入探讨 java.lang.ref 包