声明:非自己原创,只作为学习笔记,来自多人 ,勿杠
参考资料:
http://blog.csdn.net/yfqnihao
https://www.cnblogs.com/MyStringIsNotNull/p/8268351.html(推荐)
java沙箱
在了解java虚拟机的类装载器之前,我们必须知道“java的沙箱”。
为什么要使用沙箱?
程序员编写一个Java程序,默认情况下是可以访问该机器的任意资源,比如读取,删除一些文件或者网络操作等。当你把程序部署到正式的服务器上,系统管理员要为服务器的安全承担责任,那么他可能不敢确定你的程序会不会访问不该访问的资源,为了消除潜在的安全隐患,他可能有两种方法;
- 让你的程序在一个限定权限的账号下运行
- 利用Java的沙箱机制来限定你的程序不能为非作歹。
沙箱的历程:
java的沙箱总体上经历了这么一个过程,从简单的java 1.0的基础沙箱到java 1.1的基于签名和认证的沙箱到后来的基于基础沙箱+签名认证沙箱的java1.2的细粒度访问控制。
什么是沙箱?
沙箱是一个限制程序运行的环境。沙箱机制就是将Java代码限定在虚拟机(JVM)特定的运行范围内,并且严格限制代码对本地系统资源访问,通过这样的措施来保证代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源的访问,那系统资源包括什么?——CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。
也就是说
java的沙箱是你可以接受来自任何来源的代码,但是沙箱限制了它进行可能破坏系统的任何动作,因为沙箱相对于系统的总的访问能力已经被限制,所以沙箱形象的说更像是一个监狱,把有破坏能力的代码困住了。
java中的安全模型:
在Java中将执行程序分为本地代码和原创代码两种,本地代码默认视为可信任的,而远程代码则被看作是不守信的。对于授信的本地代码,可以访问一切本地资源。而对于非授信的远程代码在早期Java实现中,安全依赖于沙箱机制。如下图所示
但如此严格的安全机制也给程序的功能扩展带来了障碍,比如用户希望远程代码访问本地系统文件时候,就无法实现。因此在后续的Java1.1版本中,针对安全机制做了改进,增加了安全策略,允许用户指定代码对本地资源的访问权限。如下图所示
在Java1.2版本中,再次改进了安全机制,增加了代码签名。不论本地代码或者远程代码,都会按照用户的安全策略设定,由类加载器加载到虚拟机中权限不同的运行空间,来实现差异化的代码执行权限控制。如下图所示
当前最新的安全机制实现,则引入了域的概念。虚拟机会把所有代码加载到不同的系统域和应用域,系统域部分专门负责与关键资源进行交互,而各个应用域则通过系统域的部分代理来对各种需要的资源进行访问。虚拟机中不同的受保护域,对应不一样的权限。存在于不同域中的类文件就具有了当前域的全部权限,如下图所示
以上提到的都是基本的 Java 安全模型概念,在应用开发中还有一些关于安全的复杂用法,其中最常用到的 API 就是 doPrivileged。doPrivileged 方法能够使一段受信任代码获得更大的权限,甚至比调用它的应用程序还要多,可做到临时访问更多的资源。有时候这是非常必要的,可以应付一些特殊的应用场景。例如,应用程序可能无法直接访问某些系统资源,但这样的应用程序必须得到这些资源才能够完成功能。
java沙箱的基本组件如下:
4. 类装载器(可以由用户定制)
5. class文件检验器,也就是字节码校验器:确保Java类文件遵循Java语言规范。这样可以帮助Java程序实现内存保护。但不是所有的类文件都会经过字节码校验,比如核心类
6. 内置的java虚拟机
7. 安全管理器(可以由用户定制)
8. java核心API
java的沙箱中的类装载器和安全管理器可以由用户定制,但是这样就加大了java代码安全的风险,所以java有一个叫做访问控制体系结构,他包括安全策略规范和运行时安全策略实施,java有一个默认的安全策略管理器,用户可以使用默认的安全策略管理器,也可以在它之上继续扩展。
沙箱包含的要素:
1. 权限:允许代码执行的操作
包含三部分:权限类型、权限名和允许的操作。
2. 代码源:类所在的位置,表示以URL地址
3. 保护域:组合 代码和权限。比如代码A可以做权限B
4. 策略文件:一个策略文件包含一个或者多个保护域的项。策略文件完成了代码权限的指定任务,包括全局和用户专属两种。JVM可以使用多个策略文件,不过一般两个最常用。一个是全局的:%JRE_HOME%/lib/security/java.policy,作用于所有JVM实例。另一个是用户自己的,可以存储到用户的主目录下。
5. 秘钥库
默认沙箱
通过Java命令行启动的Java应用程序,默认不启用沙箱。
java类装载器的体系结构
java的类装载器从三方面对java的沙箱起作用:
-
它防止恶意的代码区干扰善意的代码区
如何理解这句话?不同的类装载器装入同样的类会产生一个唯一的命名空间,java虚拟机维护着这些命名空间,同一个类,一个命名空间只能装载一次,也只会装载一次,不同命名空间之间的类就如同各自有一个防护罩,感觉不到彼此的存在,如图3-1所示
-
它守护了被信任的类库边界
这里有两个需要理解的概念,
一,双亲委托模式,二,运行时包,java虚拟机通过这两个方面来界定类库的边界
什么是双亲委托模式?
这个图说明了类装载的过程。虚拟机启动的时候回启动bootStrapClassLoader,它负责加载java的核心API,然后bottStrapClassLoader会装载Launcher.java之中的ExtClassLoader(扩展类装载器),并设定其Parent为null,代表其复加载器为BootStrapLoader。ExtClassLoader去装载ext下的扩展类库,然后BootStrap Loader再要求加载Launcher.java之中的AppClassLoader(用户自定义类加载器),并设定其Parent为之前产生的ExtClassLoader实体。这两个加载器都是以静态类的形式存在的,下面我们找到java.lang.ClassLoader的loadClass这个方法
public abstract class ClassLoader {
...
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
}
这个方法告诉我们双亲委托模式的过程,当虚拟机去装载一个类的时候会先调用一个叫loadClass的方法,接着在这个方法里它会调用findLoadedClass方法来判断要装载的类字节码是否已经转入了内存。如果没有的话,它会找到它的parent(这里的parent指装载自己的那个类加载器,一般我们的应用程序类的parent是AppClassLoader),然后调用parent的loadClass,重复自己loadClass的过程。如果parent没有装载过这个这个类,就调用findBootStrapClass(这里指bootStrap,启动加载器)来尝试装载这个类的字节码,如果bootStrap也没有办法装载这个类,则调用自己的findClass来尝试装载这个类。如果还是没有办法装载,则抛出异常。
也就是说从最内层JVM自带的类加载器开始加载,外层恶意同名类得不到加载从而无法使用。
双亲委托模式的好处:
举栗子说明:
自己写一个java.lang.String类,然后在eclipse上跑一下,抛出异常:
java.lang.NoSuchMethodError: main
运行这个自定义的类的java.lang.String的双亲委托模式加载过程如下
AppClassLoader->ExtClassLoader->BootStrapLoader。由于BootStrapLoader只会加载核心API里的类,它匹配到核心API(%JAVA_HOME%\jre\lib)里的String类,所以它以为找到了这个类就直接去寻找API里的String类中的main函数,所以就抛出了异常了,而我们自己写的那个String根本没有机会被加载如内存,这样就防止了我们自己写的类对java核心代码的破坏
什么是运行时包?
设想一个问题,如果你自己定义了一个java.lang.A的类,能不能访问到java.lang.String类的friend成员?
不行,为什么?
这个就是运行时包在起作用,java的语法规定,包访问权限的成员能够被同一个包下的类访问,那么为什么不能够访问呢,这同样是为了防止病毒代码的破坏,java虚拟机只允许由同一个类装载器装载到同一个包中的类型相互访问,而由同一个类装载器,属于同一个包的,多个类型的集合就是我们所指的运行时包了。
也就是说因为严格通过包来区分了访问域,外层恶意的类通过内置代码无法获得权限访问到内层类,破坏代码就自然无法生效。
- 将代码归入某类(保护域),该类确定了代码能够执行的那些操作
除了1. 屏蔽不同的命名空间 2.保护信任类库的边界外,类装载器的第三个重要的作用就是保护域。类装载器必须把代码放入保护域中以限定这些代码运行时能够执行的操作的权限(像一个监狱一样,不让它在监狱意外的范围活动)