Java安全管理器:对通过ServiceLoader加载的外部jar代码的限制
我想要实现什么? 我正在开发一个java应用程序,它可以通过ServiceLoader集成的附加JAR进行扩展。这些加载的扩展应该在SecurityManager的限制下运行,当然只是为了提高安全性。例如,每个扩展都应该有一个特定的目录,可以在其中存储任何内容,但对任何其他文件/文件夹的访问都应该受到限制。主应用程序是受信任的代码,因此可以不受任何限制地运行。此外,主应用程序为每个扩展提供了一些api实现,这些扩展也应无限制地运行。这意味着扩展不能访问其目录之外的文件,但是当扩展调用试图访问任何其他文件的api方法时,应该授予访问权限 问题 如何实现上述行为,即只有来自扩展类的“直接”调用受到限制,而没有来自主应用程序的任何代码? 无论如何,在不同的线程/线程组中运行扩展可能是一个很好的解决方案,但由于对api的调用可能在同一线程(组)下运行,因此确定是否应仅基于线程来限制访问可能没有帮助 示例 我创建了一个简化的测试环境。一方面,有两个接口:Java安全管理器:对通过ServiceLoader加载的外部jar代码的限制,java,serviceloader,java-security-manager,Java,Serviceloader,Java Security Manager,我想要实现什么? 我正在开发一个java应用程序,它可以通过ServiceLoader集成的附加JAR进行扩展。这些加载的扩展应该在SecurityManager的限制下运行,当然只是为了提高安全性。例如,每个扩展都应该有一个特定的目录,可以在其中存储任何内容,但对任何其他文件/文件夹的访问都应该受到限制。主应用程序是受信任的代码,因此可以不受任何限制地运行。此外,主应用程序为每个扩展提供了一些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调用来做。我能想到的唯一方法是遍历调用历史并检查类加载器,以分析访问请求是基于可信代码还是基于扩展代码。但我觉得这可能是一个很容易出错的解决方案,所以我可能错过了一些更好的方法?首先确保您的“主”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
(或者通过java.security
中的policy.url.n
属性指定不同的配置),或者b)将系统ClassLoader
的实现替换为静态地将AllPermission
授予与从“main”JAR加载的类相关联的ProtectionDomain
的实现
其次,当从某个JAR加载扩展
s时,使用一个URLClassLoader
子类,该子类a)管理扩展特定的目录,b)在权限集合中包含一个java.io.FilePermission
,该权限集合静态地赋予映射到其定义类的保护域。粗略的示例实现(请注意,扩展JAR和目录之间没有持久的关系;还请注意,来自同一JAR的两个扩展
s(当然是由不同的类加载器加载的)将获得不同的目录):
有关(正确)使用“特权块”的详细信息,请参阅AccessController
和“Java SE的安全编码指南”。只要您选择AccessController
的方式,并且不要试图自己搞乱线程(阅读:尝试重新创建默认的SecurityManager
),通常应该这样做(除了一些边缘情况,例如RuntimePermission(“exitVm.*)
,这些情况需要自定义类装入器来规避)足够通过.policy文件向每个JAR授予所需的权限。extensionapimpl.doSomethingWithHigherPermissions
然后将使用AccessController.doPrivileged
仅行使其自身的权限(或其子集),而不是它自己和扩展的交集。重新考虑这个问题,而不是必须为每个“特权”提供一个API方法操作,将一些最小的、可能可变的权限集授予每个扩展
,这样它就可以直接执行所需的任何逻辑,不是更容易吗?确保有一些权限是“全部或无”(例如ReflectPermission
)因此,决不能直接授予扩展,但授予诸如对选定系统属性或文件系统子树的读取权限这样的权限难道不可行吗?是的,这可能是一个公平的观点。但这只是一个我也用于测试的简化示例。我并不真正担心系统属性。对文件系统的访问另一方面,em确实是我想要保护的一件事。因此,最终每个扩展都将获得自己的存储目录,并且对任何其他文件夹的访问都将受到扩展的限制。
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
}
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);
}
});
};