Rob*_*ker 33 java classloader securitymanager urlclassloader
编辑:更新以回应Ani B.
编辑2:添加了更新的PluginSecurityManager.
我的应用程序有一个插件机制,第三方可以提供包含实现特定接口的类的JAR.使用URLClassLoader,我能够加载该类并实例化它,没问题.因为代码可能不受信任,我需要防止它行为不端.例如,我在一个单独的线程中运行插件代码,这样如果它进入无限循环或者只是花费太长时间我就可以杀死它.但是尝试为他们设置一个安全沙箱,以便他们无法做出像网络连接或访问硬盘驱动器上的文件这样的事情让我感到非常沮丧.我的努力总是导致对插件没有影响(它具有与应用程序相同的权限)或者也限制了应用程序.我希望主应用程序代码能够完成它想要的任何东西,
关于这一主题的文献和在线资源是复杂,混乱和矛盾的.我已经阅读过各种各样的地方(例如这个问题),我需要提供一个自定义的SecurityManager,但是当我尝试它时,我遇到了问题,因为JVM延迟加载JAR中的类.所以我可以很好地实例化它,但是如果我在加载的对象上调用一个方法来实例化来自同一个JAR的另一个类,那么它就会爆炸,因为它被拒绝了从JAR读取的权利.
从理论上讲,我可以在SecurityManager中检查FilePermission,看看它是否正在尝试加载自己的JAR.这没关系,但URLClassLoader文档说:"默认情况下,加载的类只被授予访问创建URLClassLoader时指定的URL的权限." 那为什么我甚至需要一个自定义的SecurityManager?URLClassLoader不应该只处理这个吗?为什么不呢?
这是一个重现问题的简化示例:
package test.app;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import test.api.Plugin;
public class PluginTest {
public static void pluginTest(String pathToJar) {
try {
File file = new File(pathToJar);
URL url = file.toURI().toURL();
URLClassLoader cl = new URLClassLoader(new java.net.URL[] { url });
Class<?> clazz = cl.loadClass("test.plugin.MyPlugin");
final Plugin plugin = (Plugin) clazz.newInstance();
PluginThread thread = new PluginThread(new Runnable() {
@Override
public void run() {
plugin.go();
}
});
thread.start();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
Run Code Online (Sandbox Code Playgroud)
package test.api;
public interface Plugin {
public void go();
}
Run Code Online (Sandbox Code Playgroud)
package test.app;
public class PluginSecurityManager extends SecurityManager {
private boolean _sandboxed;
@Override
public void checkPermission(Permission perm) {
check(perm);
}
@Override
public void checkPermission(Permission perm, Object context) {
check(perm);
}
private void check(Permission perm) {
if (!_sandboxed) {
return;
}
// I *could* check FilePermission here, but why doesn't
// URLClassLoader handle it like it says it does?
throw new SecurityException("Permission denied");
}
void enableSandbox() {
_sandboxed = true;
}
void disableSandbox() {
_sandboxed = false;
}
}
Run Code Online (Sandbox Code Playgroud)
package test.app;
class PluginThread extends Thread {
PluginThread(Runnable target) {
super(target);
}
@Override
public void run() {
SecurityManager old = System.getSecurityManager();
PluginSecurityManager psm = new PluginSecurityManager();
System.setSecurityManager(psm);
psm.enableSandbox();
super.run();
psm.disableSandbox();
System.setSecurityManager(old);
}
}
Run Code Online (Sandbox Code Playgroud)
package test.plugin;
public MyPlugin implements Plugin {
@Override
public void go() {
new AnotherClassInTheSamePlugin(); // ClassNotFoundException with a SecurityManager
doSomethingDangerous(); // permitted without a SecurityManager
}
private void doSomethingDangerous() {
// use your imagination
}
}
Run Code Online (Sandbox Code Playgroud)
更新: 我改变它,以便在插件代码即将运行之前,它通知PluginSecurityManager,以便它知道它正在使用什么类源.然后它只允许对该类源路径下的文件进行文件访问.这也有一个很好的优势,我可以在应用程序开始时设置一次安全管理器,并在我输入和保留插件代码时更新它.
这几乎解决了这个问题,但是没有回答我的另一个问题:为什么URLClassLoader没有像我说的那样为我处理这个问题?我会暂时搁置这个问题一段时间,看看是否有人对这个问题有答案.如果是这样,该人将得到接受的答案.否则,我将把它授予Ani B.,假设URLClassLoader文档所在,并且他建议自定义SecurityManager是正确的.
PluginThread必须在PluginSecurityManager上设置classSource属性,该属性是类文件的路径.PluginSecurityManager现在看起来像这样:
package test.app;
public class PluginSecurityManager extends SecurityManager {
private String _classSource;
@Override
public void checkPermission(Permission perm) {
check(perm);
}
@Override
public void checkPermission(Permission perm, Object context) {
check(perm);
}
private void check(Permission perm) {
if (_classSource == null) {
// Not running plugin code
return;
}
if (perm instanceof FilePermission) {
// Is the request inside the class source?
String path = perm.getName();
boolean inClassSource = path.startsWith(_classSource);
// Is the request for read-only access?
boolean readOnly = "read".equals(perm.getActions());
if (inClassSource && readOnly) {
return;
}
}
throw new SecurityException("Permission denied: " + perm);
}
void setClassSource(String classSource) {
_classSource = classSource;
}
}
Run Code Online (Sandbox Code Playgroud)
来自文档:
The AccessControlContext of the thread that created the instance of URLClassLoader will be used when subsequently loading classes and resources.
The classes that are loaded are by default granted permission only to access the URLs specified when the URLClassLoader was created.
URLClassLoader就像它说的那样,AccessControlContext就是你需要看的东西.基本上,AccessControlContext中引用的线程没有权限执行您认为的操作.
我在应用程序中运行一些Groovy脚本时使用以下方法.我显然想阻止脚本运行(有意或无意)System.exit
我以通常的方式安装java SecurityManager:
-Djava.security.manager -Djava.security.policy=<policy file>
Run Code Online (Sandbox Code Playgroud)
在<policy file>
我给我的应用程序所有权限(我完全信任我的应用程序),即:
grant {
permission java.security.AllPermission;
};
Run Code Online (Sandbox Code Playgroud)
我限制运行Groovy脚本的部分的功能:
list = AccessController.doPrivileged(new PrivilegedExceptionAction<List<Stuff>> () {
public List<Stuff> run() throws Exception {
return groovyToExecute.someFunction();
}
}, allowedPermissionsAcc);
Run Code Online (Sandbox Code Playgroud)
该allowedPermissionsAcc
不会改变,因此,我在静态块创建它们
private static final AccessControlContext allowedPermissionsAcc;
static { // initialization of the allowed permissions
PermissionCollection allowedPermissions = new Permissions();
allowedPermissions.add(new RuntimePermission("accessDeclaredMembers"));
// ... <many more permissions here> ...
allowedPermissionsAcc = new AccessControlContext(new ProtectionDomain[] {
new ProtectionDomain(null, allowedPermissions)});
}
Run Code Online (Sandbox Code Playgroud)
现在棘手的部分是找到合适的权限.
如果你想允许访问某些库,你会很快意识到它们并没有用安全管理器编写,也没有非常优雅地处理它们,并且找出它们需要哪些权限可能非常棘手.如果你想通过Maven Surefire插件运行UnitTests,或者在Linux/Windows等不同平台上运行,你会遇到其他问题,因为行为可能会有所不同:-(.但这些问题是另一个话题
实现一个SecurityManager
可能是最好的方法.你必须覆盖checkPermission
.该方法将查看Permission
传递给它的对象,并确定某个操作是否危险.这样,您可以允许某些权限并禁止其他权限.
你能描述SecurityManager
你使用的自定义吗?