4

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

问题 如何实现上述行为,即仅来自扩展类的“​​直接”调用受到限制,而主应用程序中的任何代码均不受限制?无论如何,在不同的线程/线程组中运行扩展可能是一个很好的解决方案,但由于对 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 调用来做。我能想到的唯一方法是遍历调用历史并检查类加载器以分析访问请求是基于可信代码还是基于扩展代码。但我觉得这可能是一个令人讨厌的容易出错的解决方案,所以也许我错过了一些更好的方法?

4

1 回答 1

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指定不同的配置),要么 b) 将系统的实现替换为静态授予与从“主”加载的类相关联的实现罐。policy.url.njava.securityClassLoaderAllPermissionProtectionDomain

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

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

}

最后,为了使非特权Extensions 能够通过 执行特权操作ExtensionApi,后者的实现必须将特权方法(发出SecurityManager::checkXXX请求的方法)调用包装在Privileged(Exception)Actions 中并将它们传递给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);
        }
    });
};

有关(正确)使用“特权块”的详细信息,请参阅AccessController 文档和“Java SE 安全编码指南”文档

于 2018-01-04T16:00:20.060 回答