更新:这一切都使用JEXL 2.0.1完成。 您可能需要对此进行调整以使其与较新的版本一起使用。
这是我处理每种情况的方法。我已经创建了单元测试来测试每种情况,并且已经验证它们可以工作。
JEXL使这个过程非常容易。只需创建一个自定义的ClassLoader。重写两个loadClass()方法。在JexlEngine上,调用setClassLoader()。
同样,JEXL使这变得非常容易。您必须同时阻止“ .class”和“ .getClass()”。创建您自己的扩展UberspectImpl的Uberspect类。覆盖getPropertyGet,如果标识符等于“ class”,则返回null。重写getMethod,如果方法等于“ getClass”,则返回null。构造JexlEngine时,请传递对您的Uberspect实现的引用。
class MyUberspectImpl extends UberspectImpl {public MyUberspectImpl(Log jexlLog) { super(jexlLog);}@Overridepublic JexlPropertyGet getPropertyGet(Object obj, Object identifier, JexlInfo info) { // for security we do not allow access to .class property if ("class".equals(identifier)) throw new RuntimeException("Access to getClass() method is not allowed"); JexlPropertyGet propertyGet = super.getPropertyGet(obj, identifier, info); return propertyGet;}@Overridepublic JexlMethod getMethod(Object obj, String method, Object[] args, JexlInfo info) { // for security we do not allow access to .getClass() method if ("getClass".equals(method)) throw new RuntimeException("Access to getClass() method is not allowed"); return super.getMethod(obj, method, args, info);}}
您可以使用Java的AccessController机制来执行此操作。我将对此进行快速介绍。使用-Djava.security.policy = policyfile启动Java。创建一个名为policyfile的文件,其中包含以下行:grant {权限java.security.AllPermission; }; 通过以下调用设置默认的SecurityManager:System.setSecurityManager(new SecurityManager()); 现在,您可以控制权限,并且您的应用默认具有所有权限。当然,最好将应用程序的权限限制为仅需要它的权限。接下来,创建一个将访问权限限制到最低限度的AccessControlContext,并调用AccessController.doPrivileged()并传递AccessControlContext,然后在doPrivileged()中执行JEXL脚本。这是一个演示此操作的小程序。JEXL脚本调用System.exit(1),如果不是,
System.out.println("java.security.policy=" + System.getProperty("java.security.policy"));System.setSecurityManager(new SecurityManager());
try {
Permissions perms = new Permissions();
perms.add(new RuntimePermission(“accessDeclaredMembers”));
ProtectionDomain domain = new ProtectionDomain(new CodeSource( null, (Certificate[]) null ), perms );
AccessControlContext restrictedAccessControlContext = new AccessControlContext(new ProtectionDomain[] { domain } );JexlEngine jexlEngine = new JexlEngine();final script finalexpression = jexlEngine.createscript( "i = 0; intClazz = i.class; " + "clazz = intClazz.forName("java.lang.System"); " + "m = clazz.methods; m[0].invoke(null, 1); c");AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { @Override public Object run() throws Exception { return finalexpression.execute(new MapContext()); }}, restrictedAccessControlContext);}
catch (Throwable ex) {
ex.printStackTrace();
}技巧是在脚本完成之前中断脚本。我发现做到这一点的一种方法是创建一个自定义JexlArithmetic类。然后重写该类中的每个方法,并在调用超类中的real方法之前检查脚本是否应停止执行。我正在使用ExecutorService创建线程。调用Future.get()时,要经过等待时间。如果抛出TimeoutException,则调用Future.cancel(),这会中断运行脚本的线程。在新的JexlArithmetic类的每个重写的方法内部,检查Thread.interrupted(),如果为true,则抛出java.util.concurrent.CancellationException。是否有更好的位置放置将在执行脚本时定期执行的代码,以便可以将其中断?
这是MyJexlArithmetic类的节选。您必须添加所有其他方法:
public class MyJexlArithmetic extends JexlArithmetic { public MyJexlArithmetic(boolean lenient) { super(lenient); } private void checkInterrupted() { if (Thread.interrupted()) throw new CancellationException(); } @Override public boolean equals(Object left, Object right) { checkInterrupted(); return super.equals(left, right); //To change body of generated methods, choose Tools | Templates. } @Override public Object add(Object left, Object right) { checkInterrupted(); return super.add(left, right); } }这是我实例化JexlEngine的方法:
Log jexlLog = LogFactory.getLog("JEXL"); Map <String, Object> functions = new HashMap(); jexlEngine = new JexlEngine(new MyUberspectImpl(jexlLog), new MyJexlArithmetic(false), functions, jexlLog);


