副标题#e#
本文配套源码
在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 异常将被抛出,该操纵执行失败。
#p#副标题#e#
有关Java 2安详模子,有几点需要出格说明:
该模子是基于仓库授权的,这在多线程的情况下,同样合用。譬喻当父线程建设了子线程,子线程的执行被看作是父线程执行的继承,所以 Java 的安详打点器在权限查抄时,所查抄的运行时仓库,既包罗当前子线程的,也包罗从父线程哪里担任过来的运行时仓库。这意味着,用户不行能通过线程的建设来得到特另外权限。
Java 的开拓者可以利用 AccessController.doPrivileged 来优化权限查抄带来的特别机能开销。如图 3 所示,Java 的权限查抄将从仓库的顶部开始,逐一向下,直到遇到 doPrivileged 的要领挪用,可能达到仓库底部为止。利用 doPrivileged 可以制止不须要的栈遍历(Stack Traverse),提高措施的机能。
在该模子中,有一个非凡的掩护域,系统域(System Domain)。所有被 null类装载器所装载的类都被称为系统代码,其自动拥有所有的权限。并且,所有的重要的受掩护的外部资源,如文件系统、网络、屏幕、键盘等只能通过系统代码得到。
图 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
#p#分页标题#e#
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. 线程的异步协作
#p#分页标题#e#
如图 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 类,来开拓适合本身应用的安详打点器。