Java的沙箱机制原理

程序员写一个Java程序,默认的情况下你可以访问任意的机器资源,比如读取,删除一些文件或者网络操作等。当你把程序部署到正式的服务器上,系统管理员要为服务器的安全承担责任,那么他可能不敢确定你的程序会不会访问不该访问的资源,为了消除潜在的安全隐患,他可能有两种办法:1,让你的程序在一个限定权限的帐号下运行; 2.利用Java的沙箱机制来限定你的程序不能为非作歹。我们这里主要谈谈后一种方法。

Java 安全模型介绍

原理

SecurityManager

如果你经常阅读JDK源码的话,你一定经常发现类似这段的代码。

SecurityManager security = System.getSecurityManager();
if (security != null) {
    security.checkLink(libname);
}

安全管理器是一个允许应用程序实现安全策略的类。它允许应用程序在执行一个可能不安全或敏感的操作前确定该操作是什么,以及是否是在允许执行该操作的安全上下文中执行它。应用程序可以允许或不允许该操作。SecurityManager 类包含了很多名称以单词 check 开头的方法。特殊方法 checkPermission(java.security.Permission) 确定是应该允许还是拒绝由指定权限所指示的访问请求。其他所有 check 方法的默认实现都是调用 SecurityManager#checkPermission方法来确定调用线程是否具有执行所请求操作的权限。如果没有全向会抛出异常。

当前的安全管理器由 System 类中的 setSecurityManager 方法设置。当前的安全管理器由 getSecurityManager 方法获得。有时,应该在给定上下文中进行的安全检查实际上需要在不同 的上下文(例如,在一个辅助线程中)中进行。Java 为这种情况提供了包含有上下文参数的 getSecurityContext 方法和 checkPermission 方法。getSecurityContext 方法返回当前调用上下文的一个“快照”(默认的实现返回一个 AccessControlContext 对象)。checkPermission 方法使用一个上下文对象,以及根据该上下文而不是当前执行线程的上下文作出访问决策的权限。因此另一个上下文中的代码可以调用此方法,传递权限和以前保存的上下文对象。

权限分为以下类别:文件、套接字、网络、安全性、运行时、属性、AWT、反射和可序列化。管理各种权限类别的类是Permission的实现类,大部分的实现类是实现BasicPermission类。还有一个暗指所有权限的 java.security.AllPermission 权限。

现在我们看一下

// 没有指定上下文的时候,调用AccessController类来检查
public void checkPermission(Permission perm) {
    java.security.AccessController.checkPermission(perm);
}
// 指定了上下文那么就调用AccessControlContext来调用
public void checkPermission(Permission perm, Object context) {
    if (context instanceof AccessControlContext) {
        ((AccessControlContext)context).checkPermission(perm);
    } else {
        throw new SecurityException();
    }
}
// 其他方法都是基于这两个方法来实现的,这里不再赘述

java SecurityManager 的说明

AccessController

AccessController 类用于与访问控制相关的操作和决定。

基于当前生效的安全策略决定是允许还是拒绝对关键系统资源的访问

如上面的代码:

java.security.AccessController.checkPermission(perm);

在这类的实现中要特别提一下这里面有调试信息的输出,这些信息是针对安全信息,可以设置java.security.debug属性,这样的话这些信息就可以打印出来,关于这个属性的值,可以查看rt.jar当中的sun.security.util.Debug类。

将代码标记为特权调用

AccessController.doPrivileged(new LoadLibraryAction("net"));

获取当前调用上下文的“快照”,这样便可以相对于已保存的上下文作出其他上下文的访问控制决定。

AccessControlContext acc = AccessController.getContext();
// 类内部还有很多获取上下文快照的方法,这里就不一一列举了。

其实你仔细看看AccessController的源码,checkPermission方法也是先获取AccessControlContext,然后调用AccessControlContext中的checkPermission方法。

AccessControlContext

AccessControlContext 用于基于它所封装的上下文作出系统资源访问决定。

更确切地说,它封装一个上下文并且具有单个方法 (checkPermission),该方法等效于 AccessController 类中的 checkPermission 方法,只有一个不同点:AccessControlContext 的 checkPermission 方法基于它所封装的上下文而不是当前执行线程的上下文作出访问决定。

这个类在创建的时候需要传入一个保护域列表,DomainCombiner。这两个也是这个类的两个属性,用处,会在下面提到的。

这个时候我们来看一下checkcheckPermission方法,发现他是调用所有保护域的context[i].implies(perm),这才是真正检查权限的方法。

ProtectionDomain

这个里面有代码源CodeSource属性,我们查看它的implies方法,发现最后是调用了PermissionCollection的implies方法,而这个类是一个抽象的类。主要是权限集。我们仔细看看它里面有:

if (!staticPermissions &&
    Policy.getPolicyNoCheck().implies(this, permission))
    return true;
if (permissions != null)
    return permissions.implies(permission);

下面来看看Policy中的getPolicyNoCheck方法,先不管这个方法实现,它是用来加载系统的安全策略包括用户定义的部分。这个方法返回Policy对象,然后调用了implies方法,implies方法内部又调用了initPolicy方法,你会发现ProtectionDomain是由Class内部定义的getProtectionDomain本地方法获得。之后调用policy的get方法获得策略(后面会解析),再通过getPermissions(policyDomain);方法获得PermissionCollection对象,它返回的是一个内部类UnsupportedEmptyCollection的空实例。如果没有获得PermissionCollection的话,就是授予所有权限new Permissions(),这个类实现了PermissionCollection对象。

我继续看Policy的getPermission方法,它先从缓存中获取pdMapping,如果有就返回,否则domain.getPermissions()取得静态的配置,然后放入pdMapping中。放入的都是Permissions对象,所以我们再来看看implies方法,pc.implies(permission);其实调用的是Permissions的implies方法。而Permissions中的implies方法通过permsMap属性取得相应的Permission,然后调用Permission的implies方法,这个是抽象方法,需要查看具体的实现类来获得相应的内容。通过两个Permission对象的mask属性的比较,就可以获得相应的信息。

资源文件加载

我现在暂时不知道具体的实现怎么样,但是我发现在Policy的getPolicyNoCheck方法应该是读取policy文件的入口。关于Policy的自我实现策略可以查看下面的文章:

使用Policy文件来设置Java的安全策略

getPolicyNoCheck方法中先取得policy.provider属性,检查用户有没有指定自己的policyProvider(也可以自己指定自己的实现类)。如果这个属性是空的,那么默认的policyProvider是sun.security.provider.PolicyFile(这个类是Policy的实现类),然后用这个类实例化一个PolicyInfo类。然后我们没有发现具体的实现方法,这应该是预处理。

我们来看一下ProtectionDomain#implies方法,这些才是读取policy文件的入口。由于我们可以指定继承policy的Provider,但是默认的Provider是PolicyFile,我们来看一下这个类的implies方法(这也是上面原理解析到一个Policy实例吧)。

我们先看PolicyFile的构造类,首先他从java.security.auth.policy属性读取配置文件的路径。如果没有找到这个属性,那么就从java.security.manager属性中找。最后调用了init方法。里面调用了initPolicyFile方法,看一下这个方法。

首先读取policy.expandProperties属性, 安全属性文件的 "policy.expandProperties"被设为true,则可使用扩展,何字符串中都可使用,如${java.home}和{file.separator}.扩展符不能嵌套使用。policy.ignoreIdentityScope是为了忽略大小写。policy.allowSystemProperty是否允许读取系统属性。然后替换java.security.auth.policy中的占位符。

然后读取auth.policy.url.1(2,3,4......)属性,然后获得文件url。不管是url属性是如何获取的,都会调用init(URL policy)方法。这里面建立了一个PolicyParser,然后解析对应实体,比如说PolicyParser.GrantEntry等。最后将获取的PolicyEntry放入Vector policyEntries;中。然后将policyInfo的内容放入Policy类中的private static AtomicReference policy = new AtomicReference<>(new PolicyInfo(null, false));属性中。

这个时候我们再来看一下ProtectionDomain#implies方法,你会发现一切都通畅了,这里的getPolicyNoCheck方法,就是取得policy里面的值,然后如果没有初始化的话,就会去初始化,否则直接返回PolicyInfo。里面会防止一个Permissions对象。

编程实现

这里的例子我就不多说了,网上很多的。上面的例子也有。

在查找资源的过程中,我发现了这个博客,写得条理清晰。

【Java安全技术探索之路系列:Java可扩展安全架构】章节目录

【Java安全技术探索之路系列:Java可扩展安全架构】章节目录

java之jvm学习笔记

results matching ""

    No results matching ""