Java 安全管理器 : Restrictions on code from external jar loaded via ServiceLoader

标签 java serviceloader java-security-manager

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

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

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

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 调用来完成。我能想到的唯一方法是遍历调用历史并检查类加载器以分析访问请求是基于可信代码还是基于扩展代码。但我觉得这可能是一个容易出错的糟糕解决方案,所以也许我错过了一些更好的方法?

最佳答案

首先确保您的“主”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 授予 ProtectionDomain 的实现与从“主”JAR 加载的类关联。

其次,当从一些 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 执行特权操作,后者的实现必须包装特权方法(发出 SecurityManager::checkXXX 的方法 请求)Privileged(Exception)Action 中的调用并将它们传递给 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);
        }
    });
};

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

关于Java 安全管理器 : Restrictions on code from external jar loaded via ServiceLoader,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46991566/

相关文章:

java - 如何 "hide"敏感的系统属性,例如Java应用程序设置的密码?

java - 为什么需要在OSGi Security中授予java安全管理器所有权限

java - 关键高效的服务器

android - ServiceLoader.load 没有找到 META-INF/services

java - JPMS ServiceLoader 对我不起作用

java 在Java中实现动态插件

java - Spring 向 JSP 传递值

java - 为什么当我输入 END 时我的程序不会终止?

java - 为什么 Tomcat 8.5 选项 allowLinking=true 使启动更快