介绍一下Java的安全机制
2023-07-06
Java的安全好象是目前的编程语言中最优秀的一种,Java技术之所以适用于网络就是因为它有完备的、设计到其结构中的安全模式。上面我们讨论过关于类装载和命名空间等Java特有的属性,那么现在我们来看一看在实际的操作中这些特性是怎样达到安全的。
沙箱刷新程序
Java安全模式的重点在于保护最终用户不受从网上下载的破坏性程序的干扰。为达到这个目的,Java提供了一个专用的运行Java程序的沙箱。Java 程序在它的沙箱内可做任何事情,但出此边界就不能有任何操作。例如,未经确认的JavaApplet的沙箱禁止许多操作,其中包括:
* 禁止对本地磁盘的读写;
* 除了下载此Applet的主机外不能与任何别的主机连接;
* 禁止建立一个新的进程;
* 禁止载入一个直接调用本地方法的新的动态库。
通过限定下载代码的可执行操作的范围,Java安全模式可使用户免受破坏性程序的威胁。
类载入程序体系结构
在安全沙箱中JVM的一个重要方面是其类载入程序结构。在JVM中,类载入程序负责输入那些定义运行程序的类和接口的二进制数据。在图1中只有一块被标记为“类载入程序”,但事实上,在JVM内部可能有多个类载入程序。可以说,图中的类载入程序实际代表了一个可能涉及许多类载入程序的系统。JVM有非常灵活的类载入结构,它允许Java应用程序自己定义装载类的方式。
Java应用能用二种类载入程序:“原始的”类载入程序和类载入程序对象。原始的类载入程序(只有一个)是JVM的一部分。例如,如果JVM在某个操作系统上作为C程序被启用,那么原始的类载入程序就是那个C程序的一部分。原始的类载入程序装载获得确认的类,其中包括JavaAPI类,它们通常取自本地的硬盘。
在程序运行时Java应用装入类载入程序对象,它们能以自定义的方式载入类,例如通过网络来下载类文件。JVM认为它用原始的类载入程序装入的任何一个类都是已经确认的,不管它是否是JavaAPI的一部分。然而它对那些通过类载入对象装入的类则持另一种态度。在默认的情况下它认为它们是未获确认的。虽然原始的类载入程序是虚拟机运行的本质部分,但类载入对象不是。恰恰相反,类载入程序是用Java写的,编译成类文件,载入虚拟机中,然后象别的对象一样被实例化。实际上它们只是一个运行中的程序的部分执行代码。图2描述了这种结构。
因为有了类载入程序对象,在编译时你不必知道都有哪些类最终加入了Java应用程序。这样,你能在运行时动态地扩展Java应用程序。当你运行应用程序时,它能判断需要别的什么类,然后通过一个或多个类载入程序对象来装入它们。因为你是用Java编写类载入程序的,所以你能用任何方式安装类:可通过网络下载,从某些数据库中取得,甚至在乘飞机时把它算出来。
类载入程序和命名空间
JVM对每个它所载入的类都记下了是用哪种类载入程序装入的,当一个被载入的程序引用另一个类时,虚拟机要求用同一个类载入程序装入被引入的类。如果虚拟机用某一个类载入程序装入了Volcano类,它将用同样的类载入程序来装入Volcano类引用的所有类。如果Volcano引用了一个叫Java的类,也许调用了Java类的方法,虚拟机就会向装入Volcano类的载入程序要求获得Java类。载入程序返回的Java类与Volcano类是动态链接的。
因为JVM用这种方法装载类,在默认条件下某个类只能看见用同一个类载入程序装入的其它类。Java体系结构用这种方法在单个Java应用中建立多个命名空间。命名空间是一些由特定的类载入程序装入的类的独一无二的名字集合。JVM为每个类载入程序维护一个命名空间,所有由该类载入程序装入的类的名字组成了这个命名空间。
例如:一旦某个JVM把一个叫Volcano的类装入到某一特定的命名空间后,就不能再把别一Volcano类装入那个命名空间。然而你可以把多个 Volcano类装入JVM,因为你只要建立多个类载入程序就能在某个Java应用中建立多个命名空间。如果你在某个运行着的Java应用中建立了三个单独的命名空间(三个类载入程序每个载入程序一个),那么给每个命名空间装入一个Volcano类,你的应用中就有三个不同的Volcano。
Java应用能使多个类载入程序对象实例化,不管它是否来自同一个类。因此它能根据需要建立多个类载入程序对象。用不同的类载入程序装入的类在不同的命名空间中,并且除非明确许可外都不能互相访问。当你开发Java应用时,你可以把从不同来源载入的类隔离到不同的命名空间中。这样用Java的类载入程序体系结构就可控制不同来源的代码间的访问,你可以防止破坏性代码的访问正常的代码。
Applet的类载入程序
Web浏览器是用类载入程序进行动态扩展的一个例子,它用类载入程序对象从网上为某个applet下载类文件。Web浏览器启动一个装入类载入程序对象(通常称作applet类载入程序)的Java应用,这种类载入程序对象知道怎样从HTTP服务器获得类文件。Applet是动态扩展的一个例子,因为当 Java应用启动时,它并不知道浏览器会要它从网上下载哪些类文件。要下载的类文件是在运行中浏览器遇上含有Javaapplet的网页时决定的。
Web浏览器启动的Java应用通常为它索取类文件的网上站点建立不同的applet类载入程序对象。因而不同来源的类文件由不同的类载入程序对象装入,并被放入主Java应用内不同的命名空间中。因为不同来源的applet类文件放在隔离开的命名空间中,所以破坏性的代码就不能与从其他来源下载的代码直接接触。
类载入程序间的合作
通常情况下,类载入程序对象都是互相依赖以完成各自遇到的类载入需求,至少它们都依赖于原始的类载入程序。例如,假设你写了个Java应用,它安装了一个通过从网上下载类文件来获取类的载入程序。假定在运行Java应用期间要求你的类载入程序装入一个叫Volcano的类。一种实现方法是,首先让原始的类载入程序在已经确认的类库中寻找并装入该类。由于Volcano不是JavaAPI的一部分,那么原始的类载入程序就找不到它,这样你的类载入程序就会用它自定义的方式从网上下载Volcano类,假定你的类载入程序能下载Volcano类,那么它就可以在将来的应用程序的执行中发挥作 用。
下面继续同一个例子,假定一段时间后Volcano类的某个方法第一次被调用,它引用了JavaAPI中的String类,由于这是第一次调用,虚拟机要求你的类载入程序(载入Volcano的那个)装入String类。和前面一样,你的类载入程序先把要求传递给原始的类载入程序,不过这一次原始的类载入程序能直接返回String类,因为String类是很基本的类,它肯定已被用过,因此也已被装入了。大多数情况下原始的类载入程序会返回从正获确认中预先装入的String类。这样,类载入程序就不会再从网上下载它,而只是把原始的类载入程序返回的类传递给虚拟机。以后不管什么时候Volcano类要引用String 类,虚拟机都会调用已载入的这个类。
沙箱中的类载入程序
在Java沙箱中,类载入程序体系结构是阻挡破坏性代码的第一道防线。不过也正是类载入程序把可能造成破坏的代码带进JVM中。
类载入程序体系结构对Java水箱的作用有:
1. 它阻止了破坏性代码干扰良好的代码。
2. 它保护已获得确认的类库。
类载入程序结构通过识别类是否获得确认来保护类库。如果某个破坏性的类能成功地欺骗JVM,使JVM相信它是来自于JavaAPI的已获确认的类,那么它就会突破沙箱的防线。而类载入程序结构能防止未获确认的类模仿已获得确认的类,这种预防办法保障了Java运行时的安全。
命名空间和保护屏
类载入程序结构给不同的类载入程序装入的类提供了受保护的命名空间,这阻止了破坏性的代码干扰正常的代码。前面提到过,命名空间是JVM维护的被载入类的名字集合。
命名空间有助于安全性是因为你能在不同命名空间的类间放置保护屏。在JVM内部,同一个命名空间中的类能直接地互相交互,但不同命名间中的类甚至不知道对方的存在,除非明确地提供允许访问的机制。如果某个破坏性的类被装入后获得了对目前其它装入类的访问权,那这个类就有可能知道它不该知道的东西,或有可能干扰你的程序运行。
建立一个安全的环境
当你写一个使用类载入程序的应用时,你就建立了一个运行被动态装入的代码的环境。如果你想让它没有漏洞,在编写应用和类载入程序时必须遵守一些规则。总的来说要隔离破坏性的代码和正常的代码,并象保护JavaAPI一样保护获得确认的类库。
命名空间和代码来源
为了发挥命名空间对安全性的作用,你要确保用不同的类载入程序从不同的来源装入类。这就是支持Java的Web浏览器所采用的体制。Web浏览器所启动的 Java应用通常为它从网上下载类的每个来源建立不同的applet类载入程序对象。例如,某个浏览器用一个类载入程序对象从 http://www.riscapplets.com下载类,而用另一个从http://www.meanapplets.com下载类。
保护受限制的包
Java允许同一个包中的类互相授予某些访问权利,但不能对包外的类授予访问权。因此,如果你的类载入程序接到请求,要装入一个从名字上看似是 JavaAPI一部分的类(如名为Java.lang.virus的类),它就要小心处理这一请示。如果这样的类被装入的话,它就有权访问 Java.lang这个已获确认的类库,从而有可能搞破坏。
因此,你应该正式地写这样一个类载入程序,它能很简单地阻绝装入那些看似是JavaAPI一部分(或任何其它已获信任的库),却不在本地已获确认的库中的类。换名说,当你的类载入程序把请示传递给原始的类载入程序后,后者表示它不能装入这个类,你的类载入程序就应查看一下这个类有没有声明为某个已获确认的包中的一员。如果它声明了,那你的类载入程序就应该发出安全性异常消息,而不是从网上下载它。
保护被禁止的包
此外,你也许在获得信任的库中安装了一些包,你只希望你的应用通过原始的类载入程序来装入其中的类,而不想让那些你的类载入程序装入的类有访问权利。例如,假定你已经建立了一个叫absolutepower的包,并把它装到原始的类载入程序有权访问的本地库中,再假定你不希望自己的类载入程序装入的类能装入absolutepower包中的任何一个类。在这种情况下,你所写的类载入程序要做的第一件事是确保所需要的类没有声明是 absolutepower包中的一员。如果这样的类有需求的话,你的类载入程序就应发出安全性异常消息,而不是把类名传给原始的类载入程序。
类载入程序区分某个类是来自受限制的包还是来自被禁止的包的唯一办法是看它的名字。因此必须给类载入程序一张受限制的和被禁止的包的名单。因为类名 java.lang.virus表示它是来自java.lang的包,而java.lang是受限制的包,所以如果原始的类载入程序不能载入它的话,你的类载入程序就应该发出一条安全异常消息。因为类名absolutepwer.FaneyClassloader说明它属于被禁止的包 absolutepwer,你的类载入程序也应该发出安全异常消息。
注重安全性的类载入程序
写注重安全性的类载入程序的方法一般是用以下四步:
1. 如果有类载入程序无权访问的类,那么类载入程序就检查需要的类是否属于上面提到的被禁止的包。如果是,就发一条安全性异常消息,如果不是则继续第二步;
2. 类载入程序把要求转给原始的类载入程序,如果它成功地返回了类,类载入程序就返回它,否则的话继续第三步;
3. 如果有获得信任的包禁止类载入程序加入这个类,那么类载入程序就检查所要求的类是否属于受限制的包,如果是,它就发安全异常消息,否则继续第四步;
4. 类载入程序最后试着用自定义的方法装入类,例如从网上下载。如果成功的话就返回这个类,否则报告有“没有发现类定义”的错误。
类载入程序执行上述第一步和第三步保护了已获确认的包。第一步彻底防止了装入被禁止的包中的类,第三步禁止未获得确认的类把它自己添加到已获信任的包中。
结论
类载入程序的体系结构用二种方法帮助建立Java的安全模式:
1. 把代码分离到不同的命名空间并在不同命名空间的代码间设置保护屏;
2. 保护象JavaAPI这样已获确认的库。
为了发挥Java类载入程序体系结构在安全性方面的作用,程序员们必须正确使用它的上述二种功能。为利用命名空间所形成的保护,不同来源的代码应用不同的类载入程序对象来装入;为利用受信任的包得到的保护,编写的类载入程序必须对照受限制的和被禁止的包的名单来检查所需求的类的命名。