Java安全模型的第四个组成部分就是安全管理器(前三个是类装载器体系结构、class文件校验器以及Java中内置的安全特性)。
它主要用于保护虚拟机的外部资源不被虚拟机运行的恶意或有漏洞的代码侵犯。在运行的Java虚拟机中,它在访问控制——对于外部资源的访问控制——中起中枢作用。
Java安全管理器是Java中的一个类——java.lang.SecurityManager或者其子类的实例传给setSecurityManager(),以此来安装安全管理器,需要手动进行安装,如果没有安装,Java不会给你一个默认的安全管理器。当Java API进行任何可能不安全的操作时,它都会向安全管理器请求许可,从而强制执行自定义的安全策略。要向安全管理器请求许可,Java API将调用安全管理器对象的”check“方法。因为这些方法的名都以”check“开头,所以它们被称为”check“方法。例如,安全管理器的checkRead()方法决定了线程是否可以读取一个特定的文件,checkWrite()方法决定了线程能否对一个特定的文件进行写操作。
1.安装安全管理器
有两种方法来安装安全管理器:一种是直接在代码中实例化安全管理器,调用方法setSecurityManager(new SecurityManager());另一种是在命令行中使用-Djava.security.manager选项来安装管理器。
2.安全策略
制定安全管理器的安全策略也有两种方法:一种是覆盖(override)SecurityManager类中的“checkXXX”方法。
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class SecurityManagerTest extends SecurityManager{
@Override
public void checkRead(String arg0) {
// TODO Auto-generated method stub
if(arg0.equals("a.txt")){
throw new SecurityException("不能读取这个文件");
}
}
public static void main(String[] args){
System.setSecurityManager(new SecurityManagerTest());
try{
FileInputStream fis = new FileInputStream("a.txt");
System.out.println(fis.read());
} catch(FileNotFoundException e){
} catch(IOException e){
}
}
}
输出:
Exception in thread "main" java.lang.SecurityException: 不能读取这个文件
at tree.SecurityManagerTest.checkRead(SecurityManagerTest.java:13)
at java.io.FileInputStream.<init>(Unknown Source)
at java.io.FileInputStream.<init>(Unknown Source)
at tree.SecurityManagerTest.main(SecurityManagerTest.java:20)
另一种是利用.policy文件,下面重点讲一下。
3..policy文件
先看一下Java提供默认的安全策略,在%JAVA_HOME%/jre/lib/security/下的java.policy文件中。
// Standard extensions get all permissions by default
grant codeBase "file:${{java.ext.dirs}}/*" {
permission java.security.AllPermission;
};
// default permissions granted to all domains
grant {
// Allows any thread to stop itself using the java.lang.Thread.stop()
// method that takes no argument.
// Note that this permission is granted by default only to remain
// backwards compatible.
// It is strongly recommended that you either remove this permission
// from this policy file or further restrict it to code sources
// that you specify, because Thread.stop() is potentially unsafe.
// See the API specification of java.lang.Thread.stop() for more
// information.
permission java.lang.RuntimePermission "stopThread";
// allows anyone to listen on dynamic ports
permission java.net.SocketPermission "localhost:0", "listen";
// "standard" properies that can be read by anyone
permission java.util.PropertyPermission "java.version", "read";
permission java.util.PropertyPermission "java.vendor", "read";
permission java.util.PropertyPermission "java.vendor.url", "read";
permission java.util.PropertyPermission "java.class.version", "read";
permission java.util.PropertyPermission "os.name", "read";
permission java.util.PropertyPermission "os.version", "read";
permission java.util.PropertyPermission "os.arch", "read";
permission java.util.PropertyPermission "file.separator", "read";
permission java.util.PropertyPermission "path.separator", "read";
permission java.util.PropertyPermission "line.separator", "read";
permission java.util.PropertyPermission "java.specification.version", "read";
permission java.util.PropertyPermission "java.specification.vendor", "read";
permission java.util.PropertyPermission "java.specification.name", "read";
permission java.util.PropertyPermission "java.vm.specification.version", "read";
permission java.util.PropertyPermission "java.vm.specification.vendor", "read";
permission java.util.PropertyPermission "java.vm.specification.name", "read";
permission java.util.PropertyPermission "java.vm.version", "read";
permission java.util.PropertyPermission "java.vm.vendor", "read";
permission java.util.PropertyPermission "java.vm.name", "read";
};
先说明一下关于grant的语句的具体格式,如下:
grant [ SignedBy “signer_names" ] [ , CodeBase “URL" ] {
Permission permission_class_name [ “target_name" ]
[ , “action"] [, SignedBy “signer_names" ];
Permission ...
};
其中SignedBy表示的是有关数字签名的信息,暂时先不管它。在上面的java.policy中主要用到的关键字就是grant,permission_class_name,target_name和action。下面一条一条翻译:
首先第一段:
grant codeBase "file:${{java.ext.dirs}}/*"
grant是授予的意思。整个句子的意思是授予${{java.ext.dirs}}路径下的所有的class及jar文件某某某些权限。什么权限呢?看第二段:
permission java.security.AllPermission;
在.policy文件中权限是用类定义的,这种类的命名一般为xxxPermisson。对照上面的格式,其中Permmsion类似于public,private这样的声明,表示后面要定以的是权限。AllPermisson是一个permission_class_name,表示许可(permission)类的名称,每种许可类都有相应的目标(target_name)和动作(action)。AllPermission这个类表示它所授予的文件可以不受任何限制的运行。接下来:
grant {
这里并没有codeBase这个关键字,表示对任意文件(或者说是代码)授予某某某些权限。再看下一段:
permission java.lang.RuntimePermission "stopThread";
这里多了目标(target_name)这个关键字,表示RuntimePermission的目标的"stopThread",翻译过来就是任何线程都可以停止。再看下一段:
permission java.net.SocketPermission "localhost:0", "listen";
这里多了动作(action)这个关键字"listen",表示可以对运行对端口进行监听。到这里整个grant语句所有关键字都出现过了。好了,更概括性的翻译一下grant语句就是:
授予CodeBase"URL"权限,允许对target_name做action,权限属于xxxPermission类。
不过这里有些不清楚的就是为什么还需要xxxPermission类?有其它的关键字完全可以表达清楚了!这涉及到check方法的源代码,看一下checkRead()的源代码:
public void checkRead(FileDescriptor fd) {
if (fd == null) {
throw new NullPointerException("file descriptor can't be null");
}
checkPermission(new RuntimePermission("readFileDescriptor"));
}
这个方法里最终起作用的是checkPermission方法。checkPermission是AccessController类的一个方法,需要传入一个Permission类参数。好吧,再看RuntimePermission类。这个类有一个构造方法是RuntimePermission(String name)。进而想到是不是.policy文件中的target_name和action其实都是Permission类的构造函数的参数呢?继续查看API文档,发现ScoketPermission的唯一一个构造方法是
,再检查文件中剩余的Permission类发现所有的构造方法所需要的参数个数都与文件中target_name和action的个数相同。OK,这个关于.policy文件的构造,所有关键字的运用方式都了解清楚了,总结一下如图1-1所示:SocketPermission(String
host, String action)
图1-1 .policy文件构造
4.保护域
下面说一下Java是如何从类构造器开始保证程序安全的。这里面很重要的一个概念就是保护域,保护域中包含了相关类的所有权限,签名等信息,最终用来生成一个Class类实例。(还是先不管签名那些东西):
- 一个.class文件被类装载器装载时,会分析这个类的url和签名信息,生成一个CodeSource类。
- 根据.policy文件生成相应的Policy对象。
- 把CodeSource类的url传入Policy类中的getPermissons()方法,返回一个PermissionCollection对象。(这里解释一下,CodeSource类中的url对应的就是policy文件中CodeBase后跟的url,翻译过来就是:传入url,得到针对这个url的所有权限)
- 利用CodeSource类和PermissionCollection类生成一个ProtectionDomain类(保护域)。
- 最后,把ProtectionDomain对象和.class文件名传入ClassLoader中的defineClass()方法生成一个Class类的实例。
这样所用关于类的安全信息都会包含在这个Class类中,每次运行这个Class类时都会调用checkPermission()方法保护程序的安全性。整个流程如图1-2所示:
总结
安全管理器是类装载器相互配合的一种机制,可以用来判断是否能够访问文件,访问端口等操作。
其它三种机制是自动执行,而安全管理器可以由我们自己制定,但是一定要小心使用。