策略
Java安全体系结构的真正好处在于,它可以对代码授予不同层次的信任度来部分地访问系统。
Microsoft提供了ActiveX控件认证技术,它和Java的认证技术相类似,但是ActiveX控件并不在沙箱中运行。这样,使用了ActiveX,一系列移动代码要么是被完全信任的,要么是完全不被信任的。
版本1.2的安全体系结构的主要目标之一就是使建立(以签名代码为基础的)细粒度的访问控制策略的过程更为简单且更少出错。
在版本1.2的安全体系结构中,对应于整个Java应用程序的一个访问控制策略是由抽象类java.security.Policy的一个子类的单个实例所表示的。
安全策略是一个从描述运行代码的属性集合到这段代码所拥有的权限的映射。在版本1.2的安全体系结构中,描述运行代码的属性被总称为代码来源。一个代码来源是由一个java.security.CodeSource对象表示的,这个对象中包含了一个java.net.URL,它表示代码库和代表了签名者的零个或多个证书对象的数组。证书对象是抽象类java.security.cert.Certificate的子类的一个实例,一个Certificate对象抽象表示了从一个人到一个公钥的绑定,以及另一个为这个绑定作担保的人(以前提过的证书机构)。CodeSource对象包含了一个Certificate对象的数组,因为同一段代码可以被多个团体签名(担保)。这个签名通常是从JAR文件中获得的。
权限是用抽象类java.security.Permission的一个子类的实例表示的。一个Permission对象有三个属性:类型、名字和可选的操作。
在Policy对象中,每一个CodeSource是和一个或多个Permission对象相关联的。和一个CodeSource相关联的Permission对象被封装在java.security.PermissionCollection的一个子类实例中。
策略文件
java.security.Policy是一个抽象类,具体Policy子类的实现细节之一就是该子类的实例怎样知道策略应该是什么。子类可以采取多种方法,例如对一个已序列化的Policy对象进行并行化,从数据库中抽取策略,或者从文件中读取策略。由Sun提供的在Java 1.2平台下的具体Policy子类采用了最后一种方法:在一个ASCII策略文件中用上下文无关方法描述安全策略。
一个策略文件包括了一系列grant子句,每一个grant子句将一些权限授给一个代码来源。
策略文件例子policyfile.txt:
keystore "ijvmkeys";
grant signedBy "friend" {
permission java.io.FilePermission "question.txt", "read";
permission java.io.FilePermission "answer.txt", "read";
};
grant signedBy "stranger" {
permission java.io.FilePermission "question.txt", "read";
};
grant codeBase "file:${com.artima.ijvm.cdrom.home}/security/ex2/*" {
permission java.io.FilePermission "question.txt", "read";
permission java.io.FilePermission "answer.txt", "read";
};
说明:
keystore "ijvmkeys"子句说明密钥别名存储在名为ijvmkeys的keystore文件中。
第1个grant子句将授予由别名为“friend”的实体签名的所有代码两个权限。被授予的权限是:读取question.txt、answer.txt文件的权限。因为这个grant子句中没有提到代码库,所以由friend签名的代码可以来自任何代码库。
第2个grant子句只授予了读取question.txt文件的权限。
第3个grant子句将两个权限授予所有从一个特定目录中装载的代码。这个grant子句没有指明任何签名者,所以,这个代码可以是被任何人签名的,或者是未被签名的。
保护域
当类装载器将类型装入Java虚拟机时,它们将为每个类型指派一个保护域。保护域定义了授予一段特定代码的所有权限。(一个保护域对应策略文件中的一个或多个grant子句。)装载入Java虚拟机的每一个类型都属于一个且仅属于一个保护域。
访问控制器
类java.security.AccessController提供了一个默认的安全策略执行机制,它使用栈检查来决定潜在不安全的操作是否被允许。这个访问控制器不能被实例化,它不是一个对象,而是集合在单个类中的多个静态方法。AccessController的最核心方法是它的静态方法checkPermission(),这个方法决定一个特定的操作能否被允许。
如果你安装了具体安全管理器,其实最终是由这个AccessController来决定一个潜在不安全的方法是否否被允许。
每一个栈帧代表了由当前线程调用的某个方法,每一个方法是在某个类中定义的,每一个类又属于某个保护域,每个保护域包含一些权限。因此,每个栈帧间接地和一些权限相关。
1)implies()方法
为了决定由传递给AccessController的checkPermission()方法的Permission对象所代表的操作,是否包含在(或隐含在)和调用栈中的代码相关联的权限中,AccessController利用了一个名为implies()的重要方法。这个implies()方法是在Permission类以及PermissionCollection类和ProtectionDomain类中声明的。
import java.security.Permission;
import java.io.FilePermission;
import java.io.File;
// On CD-ROM in file security/ex1/Example1.java
class Example1 {
public static void main(String[] args) {
char sep = File.separatorChar;
// Read permission for "/tmp/f"
Permission file = new FilePermission(sep + "tmp" + sep + "f", "read");
// Read permission for "/tmp/*", which
// means all files in the /tmp directory
// (but not any files in subdirectories
// of /tmp)
Permission star = new FilePermission(sep + "tmp" + sep + "*", "read");
boolean starImpliesFile = star.implies(file);
boolean fileImpliesStar = file.implies(star);
// Prints "Star implies file = true"
System.out.println("Star implies file = " + starImpliesFile);
// Prints "File implies star = false"
System.out.println("File implies star = " + fileImpliesStar);
}
}
2)栈检查示例
下面几节将给出几个示例,说明AccessController执行栈检查的方法。
基于 深入理解Java虚拟机读书笔记之:第3章 安全(2)的代码示例,增加一个类:
import com.artima.security.doer.Doer;
import java.io.FileReader;
import java.io.CharArrayWriter;
import java.io.IOException;
public class TextFileDisplayer implements Doer {
private String fileName;
public TextFileDisplayer(String fileName) {
this.fileName = fileName;
}
public void doYourThing() {
try {
FileReader fr = new FileReader(fileName);
try {
CharArrayWriter caw = new CharArrayWriter();
int c;
while ((c = fr.read()) != -1) {
caw.write(c);
}
System.out.println(caw.toString());
}
catch (IOException e) {
}
finally {
try {
fr.close();
}
catch (IOException e) {
}
}
}
catch (IOException e) {
}
}
}
这个类的doYourThing()方法作用是显示一个文本文件的内容。
3)一个回答“是”的栈检查
在第一个栈检查示例中,先来看一个Example2a应用程序:
import com.artima.security.friend.Friend;
import com.artima.security.stranger.Stranger;
// This succeeds because everyone has permission to
// read answer.txt
class Example2a {
public static void main(String[] args) {
TextFileDisplayer tfd = new TextFileDisplayer("question.txt");
Friend friend = new Friend(tfd, true);
Stranger stranger = new Stranger(friend, true);
stranger.doYourThing();
}
}
当TextFileDisplayer的doYourThing()方法创建了一个新的FileReader对象时,FileReader的构造器创建了一个新的FileInputStream,FileInputStream的构造器检查是否已经安装了一个安全管理器。在现在这个例子中,已经安装了具体安全管理器,因此,FileInputStream的构造器调用了具体安全管理器的checkRead()方法。这个checkRead()方法实例化了一个新的、代表读文件question.txt权限的FilePermission对象,并将这个对象传给具体安全管理器的checkPermission()方法,这个checkPermission()方法又把这个对象传递给AccessController的checkPermission()方法。AccessController()的checkPermission()方法执行了栈检查,确定这个线程是否有权打开并读取文件question.txt。
Example2a中的栈检查:所有栈帧都有权限
调用栈的每一个栈帧用由多个元素组成的一行表示。保护域表示了这个栈帧所关联的保护域。最右边的箭头,说明了当一个AccessController的checkPermission()方法检查每一个栈帧是否有权执行被请求的操作时,它的行进方向。在箭头的左边是数字,每个栈帧对应一个。栈的顶显示在图的最底端。
FRIEND、STRANGER和CDROM三个保护域和policyfile.txt文件中的grant子句相对应,BOOTSTRAP保护域代表赋予所有由启动类装载器装载的代码的权限,在该保护域中的代码被赋予了java.lang.AllPermission,该权限允许做任何事。
运行示例代码(注:附件cdrom.zip的security/ex2目录下的ex2a.bat文件):
java -Djava.security.manager -Djava.security.policy=policyfile.txt -Dcom.artima.ijvm.cdrom.home=D:\cdrom -cp .;jars/friend.jar;jars/stranger.jar Example2a
运行结果:
Too what extent does complexity threaten security?
4)一个回答“不”的栈检查
下面一个栈检查的例子,是没有权限情况下的栈检查。
import com.artima.security.friend.Friend;
import com.artima.security.stranger.Stranger;
// This fails because the Stranger code doesn't have
// permission to read file question.txt
class Example2b {
public static void main(String[] args) {
TextFileDisplayer tfd = new TextFileDisplayer("answer.txt");
Friend friend = new Friend(tfd, true);
Stranger stranger = new Stranger(friend, true);
stranger.doYourThing();
}
}
本例子与上个例子的区别仅仅是将TextFileDisplayer的参数question.txt替换为answer.txt。
在这个例子中,检查过程并没有真正到达栈帧1,当AccessController到达栈帧2时,它发现栈帧2的doYourThing()方法属于Stranger类的代码,而这个类没有读取answer.txt文件的权限。AccessController的checkPermission()方法抛出了一个AccessControllerException异常。
Example2b中的栈检查:栈帧2没有权限
运行示例代码(注:附件cdrom.zip的security/ex2目录下的ex2b.bat文件):
java -Djava.security.manager -Djava.security.policy=policyfile.txt -Dcom.artima.ijvm.cdrom.home=D:\cdrom -cp .;jars/friend.jar;jars/stranger.jar Example2b
运行结果:
Exception in thread "main" java.security.AccessControlException: access denied (
java.io.FilePermission answer.txt read)
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.checkRead(Unknown Source)
at java.io.FileInputStream.(Unknown Source)
at java.io.FileInputStream.(Unknown Source)
at java.io.FileReader.(Unknown Source)
at TextFileDisplayer.doYourThing(TextFileDisplayer.java:19)
at com.artima.security.friend.Friend.doYourThing(Friend.java:45)
at com.artima.security.stranger.Stranger.doYourThing(Stranger.java:45)
at Example2b.main(Example2b.java:18)
5)doPrivileged()方法
有的时候,调用栈较上层(更靠近栈顶)的代码可能希望执行一段代码,而这段代码在调用栈的较下层是不允许执行的。
为了使可信的代码执行较不可靠的代码操作(这段不可靠的代码位于调用栈的较下层且没有执行这个操作的权限),AccessController类重载了四个名为doPrivileged()的静态方法。
当调用doPrivileged()方法时,就像调用其他任何方法一样,都会将一个新的栈帧压入栈。在由AccessController执行的栈检查中,一个doPrivileged()方法调用的栈帧标识了检查过程的提前终止点。如果和调用doPrivileged()的方法相关联的保护域拥有执行被请求操作的权限,AccessController将立即返回。这样这个操作就被允许,即使在栈下层的代码可能没有执行这个操作的权限。
看一看Friend的doPrivileged()的调用过程例子:
import com.artima.security.friend.Friend;
import com.artima.security.stranger.Stranger;
// This succeeds because Friend code executes a
// doPrivileged() call. (Passing false as
// the second arg to Friend constructor causes
// it to do a doPrivileged().)
class Example2c {
public static void main(String[] args) {
TextFileDisplayer tfd = new TextFileDisplayer("answer.txt");
Friend friend = new Friend(tfd, false);
Stranger stranger = new Stranger(friend, true);
stranger.doYourThing();
}
}
Example2c中的栈检查:在栈帧3停止
与前两个例子的调用栈区别在于Example2c的调用栈有两个另外的栈帧:栈帧4代表了doPrivileged()调用,栈帧5代表了PrivilegedAction对象的run()调用。
当AccessController到达栈帧4时,它发现了一个doPrivileged()调用。因此,AccessController又进行了一个检查:它检查由栈帧3代表的代码,也就是调用了doPrivileged()的代码,是否有读取文件answer.txt的权限。因为栈帧3是与FRIEND保护域相关联的,而FRIEND保护域拥有读取文件question.txt的权限,所以AccessController的checkPermission()方法正常返回。
运行示例代码(注:附件cdrom.zip的security/ex2目录下的ex2c.bat文件):
java -Djava.security.manager -Djava.security.policy=policyfile.txt -Dcom.artima.ijvm.cdrom.home=D:\cdrom -cp .;jars/friend.jar;jars/stranger.jar Example2c
运行结果:
Complexity threatens security to a significant extent. The more
complicated a security infrastructure becomes, the more likely
parties responsible for configuring security will either make
mistakes that open up security holes or avoid using the
security infrastructure altogether.
5)doPrivileged()的一个无效使用
有一点很重要,必须理解,那就是一个方法不能授予它自己比它现在已经用doPrivileged()调用所得到的权限更多的权限。通过调用doPrivileged(),一个方法仅仅能使用它现在已经被授予的权限。
作为一个doPrivileged()的无效使用的例子:
import com.artima.security.friend.Friend;
import com.artima.security.stranger.Stranger;
// This fails because even though Stranger does
// a doPrivileged() call, Stranger doesn't have
// permission to read question.txt. (Passing
// false as second arg to Stranger constructor
// causes it to do a doPrivileged().)
class Example2d {
public static void main(String[] args) {
TextFileDisplayer tfd = new TextFileDisplayer("answer.txt");
Stranger stranger = new Stranger(tfd, false);
Friend friend = new Friend(stranger, true);
friend.doYourThing();
}
}
Example2d中的栈检查:栈帧5没有权限
当AccessController到达栈帧5,它发现这个栈帧和STRANGER保护域相关联,而STRANGER保护域没有读取文件answer.txt的权限。在这种情况下,AccessController抛出一个AccessControlException异常,说明请求读取answer.txt的操作不能被执行。
运行示例代码(注:附件cdrom.zip的security/ex2目录下的ex2d.bat文件):
java -Djava.security.manager -Djava.security.policy=policyfile.txt -Dcom.artima.ijvm.cdrom.home=D:\cdrom -cp .;jars/friend.jar;jars/stranger.jar Example2d
运行结果:
Exception in thread "main" java.security.AccessControlException: access denied (
java.io.FilePermission answer.txt read)
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.checkRead(Unknown Source)
at java.io.FileInputStream.(Unknown Source)
at java.io.FileInputStream.(Unknown Source)
at java.io.FileReader.(Unknown Source)
at TextFileDisplayer.doYourThing(TextFileDisplayer.java:19)
at com.artima.security.stranger.Stranger$1.run(Stranger.java:51)
at java.security.AccessController.doPrivileged(Native Method)
at com.artima.security.stranger.Stranger.doYourThing(Stranger.java:48)
at com.artima.security.friend.Friend.doYourThing(Friend.java:45)
at Example2d.main(Example2d.java:21)
在此说明,本系列文章的内容均出自《深入理解Java虚拟机》一书,除了极少数的“注”或对内容的裁剪整理外,内容原则上与原书保持一致。由于这是一本原理性的书籍,本人不想由于自己能力与理解的问题对大家造成误解,所以除了对原书内容的裁剪整理之外,不做任何内容的延伸思考与扩展。
另外,如果您对本系列文章的内容感兴趣,建议您去阅读原版书籍,谢谢!
(转载请注明来源:http://zhanjia.iteye.com/blog/1842733)