jvm 策略和保护域
什么是java的策略,什么又是策略文件。
今天我换一下笔记的方式,不是直接讲概念,而是先来做一个小例子,相信你做完这个例子之后再看我对例子的讲解,你对策略,策略文件,会豁然开朗的感觉。
例子很简单,简单的才是大家的,下面跟着我(你完全可以copy我的代码)。
第一步,定义一个简单类。
package com.yfq.test;
import java.io.FileWriter;
import java.io.IOException;
public class TestPolicy {
public static void main(String[] args) {
FileWriter writer;
try {
writer = new FileWriter("d:/testPolicy.txt");
writer.write("hello1");
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
第二步,定义一个简单的策略文件,我们放到工程的类路径下(src文件夹里),名字为myPolicy.txt
grant codeBase "file:D:/workspace/TestPolicy/bin/*" {
permission java.io.FilePermission "d:/testPolicy.txt", "read";
};
我简单的来说一下这个文件的作用
第一行:grant codeBase "file:D:/workspace/TestPolicy/bin/*"意思是给D:/workspace/TestPolicy/bin/*给这个路径下的所有文件定义权限,星号是统配符,所有的意思
第二行:permission java.io.FilePermission "d:/testPolicy.txt", "read";意思是d:/testPolicy.txt这个文件 只分配读的权限。
第三步,运行,在cmd窗口输入(运行不起来,说明jdk的环境变量没有配置好,去配一下)
java -classpath D:/workspace/TestPolicy/bin -Djava.security.manager -Djava.security.policy=D:/workspace/TestPolicy/src/myPolicy.txt com.yfq.test.TestPolicy
这句话的意思,把当前的类路径指定为D:/workspace/TestPolicy/bin,启动默认的安全管理器(这里你应该也猜到了,策略必须和安全管理器一起合作才能起作用),
设置安全策略文件的位置(关于策略文件的安装是有多种方式的,这里我们是在windows下,如果你有兴趣可以自己再多摸索)。
第四步,查看输出
这里报出了异常,提示本应用对d:/testPolicy.txt这个文件没有写的权限。
修改一下上面的myPolicy.txt文件,如下
grant codeBase "file:D:/workspace/TestPolicy/bin/*" {
permission java.io.FilePermission "d:/testPolicy.txt", "read,write";
};
再次运行,没有报错了。这个策略文件(myPolicy.txt) 在java中对应着一个类,叫java.security.Policy(策略),这是一个神奇的类,有了它,你可以定义自己代码的权限。
策略与策略文件:
java对应用程序的访问控制策略是由抽象类java.security.Policy的一个子类的单例所表示,每个应用程序实际上只有一个Policy对象,Policy对象对应着策略文件。
类装载器利用这个Policy对象来帮助他们决定,在把一段代码导入虚拟机时应该给予什么权限。上面那段话告诉我们一个应用程序对应一个策略对象,一个策略对象对应一个策略文件。
那么策略文件,除了对我们前例中一个文件夹下的所有文件起限制作用外还能对什么主体起作用呢?先来看看下面的策略文件myPolicy.txt
keystore "ijvmkeys";
grant signedby "friend" {
permission java.io.FilePermission "d:/testPolicy.txt", "read,write";
};
grant signedby "stranger" {
permission java.io.FilePermission "d:/testPolicy.txt", "read,write";
};
grant codeBase "file:D:/workspace/TestPolicy/bin/*" {
permission java.io.FilePermission "d:/testPolicy.txt", "read,write";
};
简单的解读一下
第一行:keystore "ijvmkeys",这一行的意思,密钥对存放在当前目录一个叫ijvmkeys的文件里
第二行:grant signedby "friend",grant是授权的意思,这一行的意思是,给一个被“friend”的密钥对签名的文件授权
第三行:permission java.io.FilePermission "d:/testPolicy.txt", "read,write";这行的意思是对于d:/testPolicy.txt赋予读写的权限
倒数第三行:grant codeBase "file:D:/workspace/TestPolicy/bin/*" 这一句是对D:/workspace/TestPolicy/bin/*下的所有文件赋予权限。
重点一 :到这里我们应该可以知道,策略文件可以给一系列被签名的代码库(“friend”,‘stranger“都是代码库)授权,也可以给一个代码来源(一个具体的路径或者说url就是一个代码来源)授权。
重点二:策略文件不仅可以存储在文件中(后缀名是什么不重要),还可以存放在数据库里。
到了这里我们对策略有一个比较完整的概念了,但是你有没有这么一个疑问,前面我们总说,一个应用程序对应一个策略单例,一个策略单例对应一个策略文件,它到底怎么对应的?
下面我们就来探究一下。在探究之前,我们先引入一个新的概念叫保护域(ProtectionDomain),在笔记三的时候,我们提到过类装载器将class文件load到内存的时候会将它放置到一个保护域中,
什么是保护域
当类装载器将类型装入Java虚拟机时,它们将为每个类型指派一个保护域。保护域定义了授予一段特定代码的所有权限。(一个保护域对应策略文件中的一个或多个Grant子句。)
装载入Java虚拟机的每一个类型都属于一个且仅属于一个保护域。
类装载器知道它装载的所有类或接口的代码库和签名者。它利用这些信息来创建一个CodeSource对象。它将这个CodeSource对象传递给当前Policy对象的getPermissions()方法,
得到这个抽象类java.security.PermissionCollection的子类实例。这个PermissinCollection包含了到所有Permission对象的引用(这些Permission对象由当前策略授予指定代码来源)。
利用它创建的CodeSource和它从Policy对象得到的PermissionCollection,它可以实例化一个新的ProtectDomain对象。它通过将合适的ProtectionDomain对象传递给defineClass()方法,
来将这段代码放到一个保护域中如果你对上面这段话理解不了,看下面这个图
codeSource:代码源,这个是类装载器生成的java.security.CodeSource的一个对象,classLoader通过读取class文件,jar包得知谁为这个类签过名而封装成
一个签名者数组赋给codeSource对象的signers成员,通过这个类的来源(可能来自一个本地的url或者一个网络的url)赋给codeSource的location成员,还有这个类的公钥
证书赋给codeSource的certs成员(通常一个jar是能够被多个团体或者机构担保的,也就是我们说的认证,在java1.2的默认安全管理器还有访问控制体系结构都只能对证书起作用,
而不能对赤裸的公钥起作用,而实际上,我们用keytool生成密钥对时,同时会生成一个自签名证书,所以keytool生成的密钥对并不是赤裸的)。
如果你有疑问,我们看一下jdk里的代码
public class CodeSource implements java.io.Serializable {
private static final long serialVersionUID = 4977541819976013951L;
/**
* The code location.
*
* @serial
*/
private URL location;//本地代码库
/*
* The code signers.
*/
private transient CodeSigner[] signers = null;//签名者
/*
* The code signers. Certificate chains are concatenated.
*/
private transient java.security.cert.Certificate certs[] = null;//证书
Policy:策略,就是用来读取策略文件的一个单例对象,通过传入的CodeSource对象(由于codeSource对象里包含了签名者和代码来源)所以他通过读取grant段,取出一个个的Perssiom然后
返回一个PerssiomCollection。这个类里有一个很重要的成员变量
// Cache mapping ProtectionDomain to PermissionCollection
private WeakHashMap pdMapping;
这个成员为什么重要,我们来看一个方法
private static void initPolicy (final Policy p) {
......
if (policyDomain.getCodeSource() != null) {
.......
synchronized (p.pdMapping) {
// cache of pd to permissions
p.pdMapping.put(policyDomain, policyPerms);
}
return;
}
}
我们主要看关键代码。这个pdMapping就是把保护域对象当做key将权限集合当做value存在在了这个map里。所以我们说一个保护域对应多个策略文件的grant子句的permission。
ProtectionDomain:保护域,前面我们已经介绍过了,他就是用来容纳class文件,还有perssiom,codeSource的一个对象,如果你对此还有什么疑问,我们也看看它的代码,
来验证一下我们的结论
public class ProtectionDomain {
/* CodeSource */
private CodeSource codesource ;//代码源
/* ClassLoader the protection domain was consed from */
private ClassLoader classloader;//类装载器
/* Principals running-as within this protection domain */
private Principal[] principals;
/* the rights this protection domain is granted */
private PermissionCollection permissions;//权限集合
Permission:权限,这个对应了策略文件中一个grant子句里的一个permission,它的结构也很简单,权限名和动作,就好像我们前例中的java.io.FilePermission是一个权限名
而动作则是read和write,在Permission中它对应一个字符串。
现在我们用一张图来把上面几个概念串联起来
到这里我们已经有一条比较完整的思路了,类装载器在装载类的时候(或者执行类)会调用安全管理器,安全管理器,允许某个文件的读写啊之类的(这个在笔记九的时候我们已经做过实验了)。
那么你有没有这样的疑问,到底安全管理器是怎么样去调用策略的?这里我们不得不提出一个新的概念访问控制器AccessControl,如果你想知道访问控制器是干什么的,做什么工作,
怎么和安全管理进行合作,那么请你阅读下一节。