0.1)本文文字描述转自 core java volume 2,旨在学习 java安全 的相关知识;
1)java 技术提供了以下3种确保安全的机制(mechanism):
2)类加载器功能: 它可以在将类加载到虚拟机中的时候检查类的完整性;
1)java 编译器会为虚拟机转换源指令。 虚拟机代码存储在以 .class 为扩展名的类文件中, 每个类文件都包含某个类或者接口的定义和代码实现。 这些类文件必须有一个程序进行解释,该程序能够将虚拟机的指令集翻译成目标机器的机器语言;
Attention)虚拟机只加载程序执行时所需要的类文件。
2)看个荔枝, 假设程序从 MyProgram.class 开始运行, 下面是虚拟机运行的steps:
3)类加载机制并非只使用单个的类加载器,每个java程序至少拥有3个类加载器:
1)类加载器有一种父子关系: 除了引导类加载器外,每个类加载器都有一个父类加载器。根据规定,类加载器会为它 的父类加载器提供一个机会, 以便加载任何给定的类,并且只有在其父类加载器加载失败时,它才会加载该给定类;
2)看个荔枝: 当要求系统类加载器加载一个系统类(如, java.util.ArrayList) 时,它首先要求扩展类加载器进行加载, 该扩展类加载器则首先要求 引导类加载器进行加载。引导类加载器查找并加载 rt.jar 中的这个类, 而无需其他类加载器做更多 的搜索;
3)某些程序具有插件架构,如何加载? 如果插件被打包为 JAR 文件, 那就可以直接用 URLClassLoader 类的实例去加载这些类: (干货——引入URLClassLoader)
URL url = new URL("file://path/to/plugin.jar");
URLClassLoader loader = new URLClassLoader(new URL[]{url});
Class> c1 = loader.loadClass("mypackage.MyClass");
3.1)因为在URLClassLoader 构造器中没有指定父类加载器, 因此 loader 的父亲就是 系统类加载器。 下图展示了这种层次结构:
3.2) 大多数时候,你不必操心类加载的层次结构。 通常,类是由于其他的类需要它而被加载的, 这个过程是透明的;
4)上下文类加载器: 每个线程都有一个对类加载器的引用,称为上下文类加载器。 (干货——上下文类加载器)
Thread t = Thread.currentThread();
t.setContextClassLoader(loader);
Thread t = Thread.currentThread();
ClassLoader loader = t.getContextClassLoader();
Class c1 = loader.loadClass(className);
1)如果要编写自己的类加载器:只需要继承 ClassLoader 类,然后覆盖findClass(String className)方法;
2) ClassLoader 超类的 loadClass 方法用于将类的加载操作委托给其父类加载器去进行, 只有当该类尚未加载并且父类加载器也无法加载该类时,才调用 findClass 方法;
3)如果要实现 findClass 方法, 必须做到如下几点(key):
1)当类加载器将新加载的java 平台类的字节码传递给虚拟机时, 这些字节码首先要接受校验器的校验。
2)除了系统类外, 所有的类都要被校验。下面是校验器执行的一些检查(check):
3)in a word, 校验器的作用: 校验器总是在防范被故意篡改的类文件, 而不仅仅只是检查编译器产生的类文件;
4)看个荔枝(如何建立一个不良的类文件, 字节码校验实验):
step3) 用16进制编辑器(vim)打开该.class 文件, 并将指令3的 istore_1 改为 istore_0,也就是说,局部变量0(==m) 被初始化了两次, 而局部变量1(==n)根本没有被初始化。
0x10 ,bipush, 将单字节的常量值(-128~127)推送至栈顶
0x3b, istore_0, 将栈顶int型数值存入第一个本地变量
0x3c, istore_1, 将栈顶int型数值存入第二个本地变量
1) java平台的第二种安全机制:一旦某个类被加载到虚拟机中, 并由检验器检查之后,java 平台的第二种安全机制就会启动, 这个机制就是 安全管理器;
2)安全管理器: 是一个负责控制具体操作是否允许执行的类。安全管理器负责检查的操作包括以下内容(Content): (干货——安全管理器定义)
Attention) 在运行 java 应用程序时, 默认的设置是不安装 安全管理器的, 这样所有的操作都是允许的;
1)JDK 1.0 具有一个非常简单的安全模型: 即本地类拥有所有权限,而 远程类只能在沙盒里运行;
2)java se 1.2 开始,有了更灵活的安全机制:他的安全策略建立了代码来源 和访问权限集之间的映射关系。
4)每个类都有一个保护域:
1)策略管理器: 要读取相应的策略文件,这些文件包含了将代码来源映射为权限的指令。 (干货——策略管理器)
2)看个荔枝(一个典型的策略文件):
grant codeBase "http://www.hosrtmann.com/classes"
{
permission java.io.FilePerssion "/temp", "read, write";
}
3) 可以将策略文件 安装在 标准位置上。 默认情况下, 有两个位置可以安装策略文件(position):
Attention)
policy.url.1=file: java.home/lib/security/java.policypolicy.url.2=file: {java.home}/.java.policy
A2) 系统管理员可以修改 java.security 文件, 并可以指定 驻留在另外一台server 上且用户无法修改的 策略URL;
4)相比于修改上述的标准文件, 我们更愿意为每个应用程序配置显式的策略文件, 这样将权限写入一个独立的文件中即可;
4.1)要应用这个策略文件, 可以有两个选择selects:
4.2)在上述例子中, MyApp.policy 文件被添加到了 其他有效的策略中。 如果在命令行中添加了第二个等号, 如: java -Djava.security.policy==MyApp.policy MyApp, 那么应用程序就只使用 指定的策略文件, 而标准策略文件将被忽略;
5)default case 下, java 应用程序是不安装安全管理器的。 因此, 在安装安全管理器之前, 看不到策略文件的作用。
6)如何描述策略文件的权限:
grant codesource
{
permission1;
permission2;
};
grant codeBase "www.horstmann.com/classes/" {...};
grant codeBase "www.horstmann.com/classes/MyApp.jar" {...};
7) 权限采用下面的结构: permission className targetName, actionList ; (干货——权限的结构定义)
8)看个权限荔枝:
permission java.io.FilePermission "${user.home}${/}-", "read, write";
Attention) policytool: JDK提供了policytool的基础工具, 可以用它编辑策略文件;
1)如何把自己的权限类提供给 users, 以使得他们在策略文件中引用这些权限类;
2)实现自己的权限类: 继承 Permission类, 并提供以下方法(methods):
3)看个荔枝(请考虑下面的文件权限):
p1 = new FilePermission(“/tmp/-“, “read, write”);
3.1)该权限允许读写 /tmp 目录以及子目录中的任何文件。该权限隐含了其他更加具体的权限:
p2 = new FilePermission(“/tmp/-“, “read”);
p3 = new FilePermission(“/tmp/aFile”, “read, write”);
p4 = new FilePermission(“/tmp/aDir/”, “write”);
对以上权限的分析(Analysis):
4)执行权限检查: 应该将一个具体的文件权限对象传递给 checkPermission 方法:
checkPermission(new FilePermission(filename, “read”));
Attention) 如果你自定义了自己的权限类, 就必须对权限类定义一个合适的隐含法则(通过实现 implies 方法 来实现);
1)看个荔枝:下面这个JTextArea的子类询问安全管理器是否准备好了去添加新文本。
class WordCheckTextArea extends JTextArea
{
public void append(String text)
{
WordCheckPermission p = new WordCheckPermission(text, "insert");
SecurityManager manager = System.getSecurityManager();
if (manager != null) manager.checkPermission(p);
super.append(text);
}
}
2)单词检查权限有两个可能的操作: 一个是insert(用于插入特定文本的权限),另一个是avoid(添加不包含某些不良单词的任何文本的权限)。应该用下面的策略文件运行这个程序:
grant
{
permission WordCheckPermission "sex,drugs,C++", "avoid";
};
Attention)当设计WordCheckPermission类时,我们必须特别注意implies方法,下面是控制权限p1是否隐含p2的规则(Rules):
R1)如果p1有avoid操作,p2有insert操作,那么p2的目标必须避开p1中的所有单词。例如,下面这个权限:
WordCheckPermission “sex,drugs,C++”, “avoid”
隐含了下面这个权限:
WordCheckPermission “Mary had a little lamb”, “insert”
R2)如果p1和p2都有avoid操作,那么p2的单词集合必须包含p1单词集合中的所有单词。例如,下面这个权限:
WordCheckPermission “sex,drugs”, “avoid”
隐含了下面这个权限:
WordCheckPermission “sex,drugs,C++”, “avoid”
R3)如果p1和p2都有insert操作,那么p1的文本必须包含p2的文本。例如,下面这个权限:
WordCheckPermission “Mary had a little lamb”, “insert”
包含了下面这个权限: WordCheckPermission “a little lamb”, “insert”
Warning)务必要把你的权限类设为public。策略文件加载器不能加载位于引导类路径之外的可视包的类,并且它会悄悄忽略它无法找到的所有类。 (干货——务必要把你的权限类设为public)