因为网络允许多台计算机共享数据和分布式处理,所以它提供了一条入侵计算机系统的潜在途径,使得其他人可以窃取信息,改变或破坏信息,盗取计算机资源等等。为了解决由网络引起的安全问题,Java体系结构采用了一个扩展的内置安全模型,这个模型随着Java平台的主要版本不断发展:

1.0版本的基本沙箱
1.1版本的代码签名和认证
1.2版本的细粒度访问控制

Java安全模型侧重于保护终端用户免受从网络下载的、来自不可靠来源的、恶意程序(以及善意程序中的bug)的侵犯。为此,Java从JDK 1.0开始实现了一套沙箱环境(基本沙箱),沙箱对不可靠的活动进行了限制,程序可以在沙箱的安全边界内做任何事,但不能进行任何跨越这些边界的举动。

但由于最初的沙箱限制过于严格,善意(但不可靠)的代码常常无法进行有效工作。在版本1.1中,得到了改进,引入了基于代码签名和认证的信任模式。使得接收终端系统哦就能够可以确认一系列class文件(在一个jar文件中)已经由某一实体进行了数字签名(有效,可被信赖),并且在签名过后,这些class文件没有改动,这使得终端用户和系统管理员减少了对某些代码在沙箱中的限制,但这些代码必须已由可信任团体进行数字签名。

虽然版本1.1中发布的安全API包含了对认证的支持,但是实际上,除了提供完全信任和完全不信任策略(换句话说,代码要么完全被信任,要么我完全不被信任)以外,没有提供其他实际帮助。后面的1.2版本提供的API可以帮助建立细粒度的安全策略。

下面详细看看基本沙箱、代码签名和认证、细粒度访问控制是如何实现的。

基本沙箱

Java沙箱 安全建立在 Java 运行时环境的三个基本方面的基础上:ByteCode Verifier(字节码验证器)、Security Manager(安全管理器)以及 ClassLoader(类装入器)。

ByteCode Verifier:它确保所下载的代码被恰当地格式化,字节码(“Java 虚拟机”指令)没有违反这种语言或虚拟机的安全限制(无非法数据转换),没有执行指针寻址,内部堆栈不能溢出或下溢,以及字节码指令将拥有正确的类型参数。

Security Manager:它在尝试执行文件 I/O 和网络 I/O、创建新的ClassLoader、操作线程或线程组、启动底层平台(操作系统)上的进程、终止“Java 虚拟机”、将非 Java 库(本机代码)装入到 JVM、完成某种类型的窗口系统操作以及将某种类型的类装入到 JVM 中时发起运行时访问控制。

ClassLoader:Java程序(class文件)并不是本地的可执行程序。当运行Java程序时,首先运行JVM(Java虚拟机),然后再把Java class加载到JVM里头运行,负责加载Java class的这部分就叫做Class Loader。当运行一个程序的时候,JVM启动,运行bootstrapclassloader,该ClassLoader加载java核心API(ExtClassLoader和AppClassLoader也在此时被加载),然后调用ExtClassLoader加载扩展API,最后AppClassLoader加载CLASSPATH目录下定义的Class,这就是一个程序最基本的加载流程。

代码签名和认证

Java1.1的java.security包中引入的认证策略,认证能帮助用户通过实现一个沙箱来建立多种安全策略。认证可以使用户相信由某些团体担保的class文件具有高度的可信度,并且这些class文件没有在到达Java虚拟机之前的网络传输中被改变。这样,就可以简化沙箱对被认证代码的限制,针对由不同团体签名的代码建立不同的安全限制。

要对一段代码进行担保签名,首先要生成一个公钥/私钥对。用户要公开公钥,保管私钥。之后将要签名的class文件和其他文件放在一个JAR文件中,然后用诸如SDK中的jarsigner 工具对整个JAR文件签名。这个签名工具将首先通过单项散列计算,对JAR文件产生一个散列,然后用私钥对这个散列签名,并在JAR文件的末尾加上这个签名,从而完成你对这个JAR文件的数字签名。

数字签名的散列计算中大量输入的是组成JAR文件内容的字节流,产生的是不能包含所有输入信息的少量数据。这个计算是单向的:从大到小,从输入到散列。为了加强安全性,要用私钥进行加密。因为私钥加密是一个非常费时的过程,我们只对散列进行私钥的加密。公钥/私钥对具有如下特点:在仅给出公钥的情况下时,想要产生私钥是非常困难的,而且任何用私钥加密的代码都可以用配对的公钥才能解密。因为不同的输入可能产生相同的散列,而产生相同散列的概率主要依赖于散列的大小。在实际情况下,散列主要采用64位或128位,这样的长度要想从不同的输入中产生一个相同的散列的计算是不可行的。之后将这个加密后的散列值加到同一个JAR文件中,这个JAR文件还包含了你最初产生这个散列的文件。

接受者必须用公钥对签名散列进行解密,通过得到的结果和从JAR文件计算得到的散列是否相同,可以验证一个已经签名的JAR文件。如果得到的散列值和解密的散列值匹配,则表明了接受者收到的JAR文件确实被发送者所担保,并且传输过程中没有被修改,这个文件安全性是有保障的。这样,就可以将这个JAR文件放入安全级别不很严格的一般沙箱里,这个沙箱信任发送者的签名。

细粒度访问控制

版本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的一个子类实例中。