Java安全管理器:通过ServiceLoader

时间:2017-10-28 15:49:28

标签: java serviceloader java-security-manager

我想要实现的目标是什么? 我正在开发一个java应用程序,可以通过ServiceLoader集成的其他jar扩展。这些加载的扩展应该由SecurityManager运行一些限制,当然只是为了提高安全性。例如,每个Extension都应该获得一个特定的目录,它可以存储任何内容,但是应该限制对任何其他文件/文件夹的访问。主应用程序是可信代码,因此可以不受任何限制地运行。此外,主应用程序为每个扩展提供了一些api实现,它们也可以不受限制地运行。这意味着扩展程序不能访问其目录之外的文件,但是当扩展程序调用尝试访问任何其他文件的api方法时,应该授予访问权限。

问题 如何才能实现上述行为,只有来自扩展类的“​​直接”调用受到限制,而不是来自主应用程序的任何代码? 无论如何,在不同的线程/ threadGroup中运行扩展可能是一个很好的解决方案,但由于对api的调用可能在同一个线程(组)下运行,因此可能无法确定是否应该仅基于线程来限制访问。

示例 我创建了一个简化的测试环境。一方面有这两个接口:

public interface Extension {
    void doSomethingRestricted();
    void doSameViaApi(ExtensionApi api);
}

public interface ExtensionApi {
    void doSomethingWithHigherPermissions();
}

为了测试,我创建了一个包含此扩展名的jar:

public class SomeExtension implements Extension {

    public void doSomethingRestricted() {
        System.out.println(System.getProperty("user.home"));
    }

    public void doSameViaApi(final ExtensionApi api) {
        api.doSomethingWithHigherPermissions();
    }
}

在主应用程序中,我想做这样的事情:

final ExtensionApi api = () -> System.out.println(System.getProperty("user.home"));
try {
    final URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { jarFile.toURI().toURL() });
    for(final Extension extension : ServiceLoader.load(Extension.class, urlClassLoader)) {
        extension.doSomethingRestricted();
        extension.doSameViaApi(api);
    }
}

因此,当我调用extension.doSomethingRestricted();时,它应该导致SecurityException,但调用extension.doSameViaApi(api);应该可以正常工作。 所以这两种方法都试图做同样的事情,但有人试图通过api调用来做到这一点。我能想到的唯一方法是遍历调用历史记录并检查类加载器以分析访问请求是基于可信代码还是扩展代码。但我觉得这可能是一个讨厌的容易出错的解决方案,所以也许我错过了一些更好的方法?

1 个答案:

答案 0 :(得分:2)

首先确保您的“主要”JAR课程享有完整的特权。以编程方式,这可以完成如下:

package q46991566;

import java.nio.file.Files;
import java.nio.file.Path;
import java.security.Policy;
import java.util.Collections;

public class Main {

    public static void main(String... args) throws Exception {
        // policy configuration contents: this JAR gets all permissions, others get nothing
        StringBuilder sb = new StringBuilder("grant {};\n\ngrant codebase \"")
                .append(Main.class.getProtectionDomain().getCodeSource().getLocation())
                .append("\" {\n\tpermission java.security.AllPermission;\n};\n");
        // temp-save the policy configuration
        Path policyPath = Files.createTempFile(null, null);
        Files.write(policyPath, Collections.singleton(sb.toString()));
        // convey to the default file-backed policy provider where to obtain its configuration from;
        // leading equals ensures only the specified config file gets processed
        System.setProperty("java.security.policy", "=".concat(policyPath.toUri().toURL().toString()));
        // establish a policy; "javaPolicy" is the default provider's standard JCA name
        Policy.setPolicy(Policy.getInstance("javaPolicy", null));
        // policy loaded; backing config no longer needed
        Files.delete(policyPath);
        // establish a security manager for enforcing the policy (the default implementation is more than
        // sufficient)
        System.setSecurityManager(new SecurityManager());

        // ...
    }

}

或者,你要么必须a)修改JRE发行版的java.policy(或通过policy.url.n中的java.security属性指定不同的配置),或b)替换执行系统ClassLoader,其中一个静态地将AllPermission授予与从“主”JAR加载的类相关联的ProtectionDomain

其次,从某个JAR加载Extension时,使用URLClassLoader子类a)管理特定于扩展名的目录,b)在权限集合中包含java.io.FilePermission静态符合映射到其定义的类的保护域。原始示例实现(请注意,扩展JAR和目录之间没有持久关系;还要注意,源自同一JAR的两个Extension(当然,由不同的类加载器加载)将获得不同的目录) :

package q46991566;

import java.io.FilePermission;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.cert.Certificate;
import java.util.Enumeration;
import java.util.Objects;

public final class ExtensionLoader extends URLClassLoader {

    private static void copyPermissions(PermissionCollection src, PermissionCollection dst) {
        for (Enumeration<Permission> e = src.elements(); e.hasMoreElements();) {
            dst.add(e.nextElement());
        }
    }

    private final CodeSource origin;
    private final PermissionCollection perms = new Permissions();
    private final Path baseDir;

    public ExtensionLoader(URL extensionOrigin) {
        super(new URL[] { extensionOrigin });
        origin = new CodeSource(Objects.requireNonNull(extensionOrigin), (Certificate[]) null);
        try {
            baseDir = Files.createTempDirectory(null);
            perms.add(new FilePermission(baseDir.toString().concat("/-"), "read,write,delete"));
            copyPermissions(super.getPermissions(origin), perms);
            perms.setReadOnly();
        }
        catch (IOException ioe) {
            throw new RuntimeException(ioe);
        }
    }

    @Override
    protected PermissionCollection getPermissions(CodeSource cs) {
        return (origin.implies(cs)) ? perms : super.getPermissions(cs);
    }

    // ExtensionApiImpl (or ExtensionImpl directly -- but then ExtensionLoader would have to be relocated
    // into a separate, also fully privileged JAR, accessible to the extension) can call this to relay to
    // extensions where they can persist their data
    public Path getExtensionBaseDir() {
        return baseDir;
    }

    // optionally override close() to delete baseDir early

}

最后,对于无特权的Extension能够通过ExtensionApi执行特权操作,后者的实现必须在{{1}内包装特权方法(发出SecurityManager::checkXXX个请求的方法)调用并将它们传递给Privileged(Exception)Action; e.g:

AccessController::doPrivileged

有关(特权)使用“特权块”的详细信息,请参阅ExtensionApi api = () -> { AccessController.doPrivileged((PrivilegedAction<Void>) () -> { try { Files.write(Paths.get("/root/Documents/highly-sensitive.doc"), Collections.singleton("trusted content"), StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.APPEND); return null; } catch (IOException ioe) { throw new RuntimeException(ioe); } }); }; documentation和“Java SE安全编码指南”document