第 8 章 程序运行的安全性
——基于代码来源的授权
本章重点:
当用户在计算机中执行 Java 程序时,很关心这个 Java 程序是否是安全的。不安全的程
序在运行时可能泄漏一些信息(比如可能读取“我的文档”中的文件并通过网络秘密发送出
去),也可能删除你的一些重要文件。
本章介绍一种解决方法:根据代码的来源(存放在什么位置或所有者是谁)来设置代
码可以有那些权限。
本章主要内容:
使用安全管理器来运行程序
编写自己的安全管理器
根据代码的 URL 进行各种授权
使用 jarsigner 工具对代码进行签名
根据代码的签名者进行各种授权
多个代码相互调用中定义特权代码
定义自己的权限
对 Java Applet 进行签名
使用 Java Plug-in 运行 Java Applet
基于策略文件和 RSA 签名控制 Java Applet
8.1 安全管理器的使用
本节首先给出通过 Java 命令行选项指定默认安全管理器实现程序运行的安全的实例 ,
然后给出如何定义自己的安全管理器满足特殊需要,最后给出程序中指定安全管理器的方
法。
8.1.1 使用默认的安全管理器限制应用程序
★ 实例说明
本实例先给出一个简单的 Java 程序,在一定条件下它会偷偷读取用户目录 C:盘根目录
258
autoexec.bat 文件的内容。然后给出如果用户不希望自己的计算机被 Java 程序任意读写,如
何才能禁止 Java 程序进行所有这类的操作。
★ 编程思路:
本实例的程序只是一个简单的读取文件的程序,本节重点在如何运行程序。
该程序可能是一个大型的程序,其中可能某段代码包含了读取用户文件的操作。程序
中 ShowFile 类的 go(String name)方法中,通过参数中的文件名称生成 FileReader 类型的对
象,继而生成 BufferedReader 类型的对象,通过 BufferedReader 对象的 readLine( )方法读取
文件中的内容。
为了使本示例更有代表性,另外定义了一个类 RunShowFile 作为主程序,它从命令行
参数读入要读取的文件的名称,然后创建 ShowFile 类,调用其 go( )方法显示指定的文件的
内容。
此外主程序还做了一个陷阱:如果用户输入的文件名以“ .txt”为后缀,则偷偷读取 c:\
autoexec.bat 文件的内容,然后偷偷做各种事情(示例程序不妨偷偷做哪些事情忽略,只演
示确实读取了 c:\autoexec.bat 文件的内容)。程序中使用的是“c:\\autoexec.bat”,这是因为
Java 字符串中斜杠“\”代表转义,字符串中的斜杠要用双斜杠“\\”表示。
★代码与分析:
完整程序如下:
import java.io.*;
public class ShowFile{
public String go(String name) throws IOException{
String s;
String content="";
BufferedReader in;
in = new BufferedReader(new FileReader(name));
while ((s = in.readLine( )) != null) {
content+=s+"\n";
}
return content;
}
}
public class RunShowFile{
public static void main(String args[]) throws IOException{
ShowFile t=new ShowFile();
String s=t.go(args[0]);
if(args[0].endsWith(".txt")){
String s2=t.go("c:\\autoexec.bat");
}
// 使用 s2 做各种事情
System.out.println(s);
System.out.println("Over");
}
}
259
这两个类分别放在两个文件中。
★运行程序
程 序 编 写 者 输 入 “ javac *.java” 编 译 程 序 , 当 前 目 录 下 将 新 出 现 两 个 文 件 :
ShowFile.class 和 RunShowFile.class。然后将这两个文件提供给使用者,告诉使用者:运行
RunShowFile.class,则显示参数中指定的文件的内容。但程序编写者隐瞒了如果用户输入
的文件名以“.txt”为后缀,则偷偷读取 c:\autoexec.bat 文件的内容的陷阱。
当用户得到 ShowFile.class 和 RunShowFile.class 文件后,在不考虑安全性问题时,可能
只是输入:
java RunShowFile RunShowFile.java
则将显示当前目录下 RunShowFile.java 文件的内容。
或输入:
java RunShowFile C:\j2sdk1.4.0\README.txt
则将显示 C:\j2sdk1.4.0\README.txt 文件的内容。
用户也可以在当前目录建立一个.txt 为后缀的文件,如文件名可以使用“1.txt”,随便输
一些内容以便进行测试。则输入
java RunShowFile 1.txt
将显示当前目录下 1.txt 文件的内容。
但用户并不知道,在其输入“java RunShowFile C:\j2sdk1.4.0\README.txt”或者“java
RunShowFile 1.txt”时,用户机器上 c:\autoexec.bat 文件的内容已经被泄漏了。更严重的是,
只要程序开发者愿意,程序其实可能在你不知不觉中任意修改、查看你的计算机系统中任
何信息。如果这个程序是从一些不可靠的地方得来的,如从网上随便下载的,则这种安全
问题可能更严重。
各种语言编写的程序都会面临着这一问题,尽管用户可以分析程序的源代码或进行各
种监控,但当程序很大,这种工作是非常繁琐且不可靠的。而 Java 使用安全管理器较好地
解决了这一问题。
只要在运行 Java 程序时指定一个 Java 命令行选项:-Djava.security.manager,则将使用
Java 缺省的安全管理器 java.lang.SecurityManager 来强制进行各种安全检查,如输入:
java -Djava.security.manager RunShowFile C:\j2sdk1.4.0\README.txt
运行程序,则将显示:
Exception in thread "main" java.security.AccessControlException: access denied (
java.io.FilePermission C:\j2sdk1.4.0\README.txt read)
at
java.security.AccessControlContext.checkPermission(AccessControlContext.java:270)
at java.security.AccessController.checkPermission(AccessController.java:401)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:542)
at java.lang.SecurityManager.checkRead(SecurityManager.java:887)
at java.io.FileInputStream.<init>(FileInputStream.java:100)
at java.io.FileInputStream.<init>(FileInputStream.java:66)
at java.io.FileReader.<init>(FileReader.java:39)
at ShowFile.go(RunShowFile.java:7)
at RunShowFile.main(RunShowFile.java:19)
其中第二行指出,程序对 c:\j2sdk1.4.0\readme.txt 文件没有读的许可(权限)。此时刚
260
开始访问 c:\j2sdk1.4.0\readme.txt,尚未执行到访问 c:\autoexec.bat 的语句,就已经被禁止访
问了。
输入
java -Djava.security.manager RunShowFile 1.txt
运行程序,则将显示:
Exception in thread "main" java.security.AccessControlException: access denied (
java.io.FilePermission c:\autoexec.bat read)
at java.security.AccessControlContext.checkPermission(AccessControlConte
xt.java:270)
at java.security.AccessController.checkPermission(AccessController.java:
401)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:542)
at java.lang.SecurityManager.checkRead(SecurityManager.java:887)
at java.io.FileInputStream.<init>(FileInputStream.java:100)
at java.io.FileInputStream.<init>(FileInputStream.java:66)
at java.io.FileReader.<init>(FileReader.java:39)
at ShowFile.go(RunShowFile.java:7)
at RunShowFile.main(RunShowFile.java:21)
其中第二行指出,程序对 c:\autoexec.bat 文件没有读的权限。此时,访问 1.txt 文件已
经通过,因为 1.txt 在当前目录。而程序偷偷访问的 c:\autoexec.bat 不在当前目录,因此被
禁止访问。
只有输入
java -Djava.security.manager RunShowFile RunShowFile.java
因为 RunShowFile.java 在当前目录,因此可以正常运行,程序将显示 RunShowFile.java 文
件的所有内容。
8.1.2 编写自己的安全管理器
★ 实例说明
上 一 节 通 过 Java 命 令 行 选 项 “ -Djava.security.manager” 使 用 缺 省 的 安 全 管 理 器
java.lang.SecurityManager 来强制进行各种安全检查,本实例先给出如何自己编写这样一个
安全管理器,从中可以了解 Java 安全管理器的工作细节。
★ 编程思路:
仍旧使用 8.1.1 小节的程序,只是安全管理器使用我们自己定义的,而不是缺省的
java.lang.SecurityManager 类。
为 了 定 义 自 己 的 安 全 管 理 器 , 只 要 定 义 java.lang.SecurityManager 类 的 子 类 即 可
java.lang.SecurityManager 类中有大量 checkXXX( )形式的方法,Java 类库中各个类的方法
凡是执行敏感的操作时,都会先获取安全管理器,执行安全管理器相应的 checkXXX( )方
法检验看是否有某种权限。
如 8.1.1 小节的程序使用了 FileReader 类,我们可以使用 WinZip 工具打开 J2SDK 安装
目录 C:\j2sdk1.4.0 下的 src.zip 文件,该文件是 Java 类库的源代码,从中找到 FileReader.java
文 件 查 看 其 源 代 码 , 会 发 现 FileReader 类 的 构 造 器 中 执 行 了 super(new
261
FileInputStream(fileName)),继续打开 src.zip 文件中的 FileInputStream.java 文件,会发现其
构造器中有如下代码:
public FileInputStream(File file) throws FileNotFoundException {
String name = file.getPath();
SecurityManager security = System.getSecurityManager( );
if (security != null) {
security.checkRead(name);
}
可 见 , 在 我 们 编 写 程 序 执 行 new FileReader(name) 时 , 程 序 已 经 自 动 通 过
System.getSecurityManager( )方法得到了安全管理器,并执行了其中的 checkRead( )方法检
查是否有权限。如果检查没有通过,checkRead( )方法将扔出异常对象。
因此,我们编写自己的安全管理器时,只要将缺省的 java.lang.SecurityManager 类中满
足不了自己需要的 checkXXX( )方法重写即可。
如对 8.1.1 小节的例子,默认的安全管理器不允许访问 c:\autoeec.bat,也不允许访问
C:\j2sdk1.4.0\README.txt 文件。如果希望能够读 C 所有的“.txt”和“.java”后缀的文件,则可
以重写 checkRead( )方法,判断方法参数传入的文件名是否以“.txt”或“.java”后缀为后缀。由
于程序在运行过程中还需要读其他类,因此同时要判断文件名是否以“ .class”为后缀,以及
文件是否在“C:\j2sdk1.4.0”或其子目录(如果是其他版本的 J2SDK,则应修改为相应的安装
目 录 ) 。 如 果 条 件 都 不 满 足 , 则 扔 出 SecurityException 类 型 的 异 常 对 象 , 在 创 建
SecurityException 对象时可通过构造器的参数指定产生异常的原因。
★代码与分析:
完整程序如下:
import java.io.*;
public class MySecurityManager extends SecurityManager {
public void checkRead(String file) {
if ( !(file.endsWith(".txt"))
&& !(file.endsWith(".java"))
&& !(file.endsWith(".class"))
&& !(file.startsWith("C:\\j2sdk1.4.0")) ) {
throw new SecurityException ("No Read Permission for : " + file);
}
}
}
★运行程序
运行程序时可在 java 命令行选项-Djava.security.manager 后加上等于号“=”,将所编写的
自己的安全管理器的类名赋值给 java.security.manager 即可。
如输入
java -Djava.security.manager=MySecurityManager RunShowFile RunShowFile.java
运行程序,则使用 MySecurityManager 类型的对象作为安全管理器,由于它允许读所有
“.java”后缀的文件,因此程序和 8.1.1 小节一样可以正常显示 RunShowFile.java 文件的内容。
262