做蛋糕网站的优点,网站建设栏目层级,校园网络及网站建设,监利县建设局网站最近的Activiti 5.21.0版本的突出特点之一是“安全脚本”。 Activiti用户指南中详细介绍了启用和使用此功能的方法 。 在这篇文章中#xff0c;我将向您展示我们如何实现其最终实现以及它在幕后所做的事情。 当然#xff0c;由于这是我通常的签名风格#xff0c;因此我们还将… 最近的Activiti 5.21.0版本的突出特点之一是“安全脚本”。 Activiti用户指南中详细介绍了启用和使用此功能的方法 。 在这篇文章中我将向您展示我们如何实现其最终实现以及它在幕后所做的事情。 当然由于这是我通常的签名风格因此我们还将对性能进行一些了解。 问题 长期以来Activiti引擎一直支持脚本任务和任务/执行侦听器的脚本编写。 所使用的脚本在流程定义中定义并且可以在部署流程定义后直接执行。 这是很多人喜欢的东西。 这与Java委托类或委托表达式有很大的不同因为它们通常需要将实际的逻辑放在类路径上。 它本身已经引入了某种“保护”因为高级用户通常只能这样做。 但是使用脚本不需要这种“额外步骤”。 如果您将脚本任务的功能提供给最终用户并且我们从某些用户那里知道某些公司确实拥有此用例那么所有的赌注都将大打折扣。 您可以通过执行流程实例来关闭JVM或执行恶意操作。 第二个问题是编写一个无限循环且永无止境的脚本非常容易。 第三个问题是脚本在执行时可以轻松使用大量内存并占用大量系统资源。 让我们来看入门的第一个问题。 首先让我们添加最新和最大的Activiti引擎依赖性以及内存数据库库中的H2 dependenciesdependencygroupIdorg.activiti/groupIdartifactIdactiviti-engine/artifactIdversion5.21.0/version/dependencydependencygroupIdcom.h2database/groupIdartifactIdh2/artifactIdversion1.3.176/version/dependency
/dependencies 我们将在这里使用的过程非常简单只是一个开始事件脚本任务和结束。 此处的过程并不是真正的重点而是脚本执行。 我们将尝试的第一个脚本有两件事它会获取并显示我的机器的当前网络配置但显然有此想法的更危险的应用程序 然后关闭整个JVM 。 当然在适当的设置中可以通过确保运行逻辑的用户在计算机上没有任何重要权限但不能解决占用资源的问题来缓解其中的某些问题。 但是我认为这很好地说明了为什么将脚本的功能提供给几乎任何人实际上在安全方面是很糟糕的。 scriptTask idmyScriptTask scriptFormatjavascriptscriptvar s new java.util.Scanner(java.lang.Runtime.getRuntime().exec(ifconfig).getInputStream()).useDelimiter(\\A);var output s.hasNext() ? s.next() : ;java.lang.System.out.println(--- output output);java.lang.System.exit(1);/script
/scriptTask 让我们部署流程定义并执行流程实例 public class Demo1 {public static void main (String[] args) {// Build engine and deployProcessEngine processEngine new StandaloneInMemProcessEngineConfiguration().buildProcessEngine();RepositoryService repositoryService processEngine.getRepositoryService();repositoryService.createDeployment().addClasspathResource(process.bpmn20.xml).deploy();// Start process instanceRuntimeService runtimeService processEngine.getRuntimeService();runtimeService.startProcessInstanceByKey(myProcess);}
} 给出以下输出此处缩短 —输出 eth0链接encap以太网 inet地址192.168.0.114广播192.168.0.255掩码255.255.255.0 … 流程以退出代码1完成 它输出有关我所有网络接口的信息然后关闭整个JVM。 是的。 太恐怖了 尝试纳斯霍恩 第一个问题的解决方案是我们需要将要在脚本中公开的内容列入白名单并且默认情况下将所有内容都列入黑名单。 这样用户将无法运行任何可以做恶意事情的类或方法。 在Activiti中当javascript脚本任务是流程定义的一部分时我们使用JDK中的ScriptEngine类将此脚本提供给JDK中嵌入的javascript引擎。 在JDK 6/7中是Rhino在JDK 8中是Nashorn。 首先我进行了一些认真的搜索以找到Nashorn的解决方案因为这将更加适用于未来。 Nashorn确实具有“类过滤器”概念可以有效地实施白名单。 但是ScriptEngine抽象没有任何工具可以实际调整或配置Nashorn引擎。 我们必须做一些底层的魔术才能使其正常工作。 代替使用默认的Nashorn脚本引擎我们自己在“ SecureScriptTask”这是常规的JavaDelegate中实例化Nashorn脚本引擎。 注意使用jdk.nashorn。*包的用法–不太好。 我们遵循https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/nashorn/api.html中的文档通过在Nashorn引擎中添加“ ClassFilter”来使脚本执行更加安全。 这实际上是可以在脚本中使用的已批准类的白名单。 public class SafeScriptTaskDemo2 implements JavaDelegate {private Expression script;public void execute(DelegateExecution execution) throws Exception {NashornScriptEngineFactory factory new NashornScriptEngineFactory();ScriptEngine scriptEngine factory.getScriptEngine(new SafeClassFilter());ScriptingEngines scriptingEngines Context.getProcessEngineConfiguration().getScriptingEngines();Bindings bindings scriptingEngines.getScriptBindingsFactory().createBindings(execution, false);scriptEngine.eval((String) script.getValue(execution), bindings);System.out.println(Java delegate done);}public static class SafeClassFilter implements ClassFilter {public boolean exposeToScripts(String s) {return false;}}} 当执行时上面的脚本将不会执行将引发一个异常指出“线程“主”中的异常java.lang.RuntimeExceptionjava.lang.ClassNotFoundExceptionjava.lang.System.out.println”。 请注意ClassFilter仅可从JDK 1.8.0_40使用相当早。 但是这不能解决无限循环的第二个问题。 让我们执行一个简单的脚本 while (true) {print(Hello);
} 您可以猜测会做什么。 这将永远运行。 如果幸运的话脚本任务在事务中执行时事务超时将发生。 但这远不是一个体面的解决方案因为它浪费了一段时间的CPU资源而无所作为。 使用大量内存的第三个问题也很容易证明 var array []
for(var i 0; i 2147483647; i) {array.push(i);java.lang.System.out.println(array.length);
} 启动流程实例时内存将快速填满仅以几个MB开头 并最终以OutOfMemoryException 线程“ main”中的异常java.lang.OutOfMemoryError超出了GC开销限制 切换到犀牛 在下面的示例与上一个示例之间花费了大量时间使Nashorn以某种方式拦截或应对无限循环/内存使用情况。 但是经过大量搜索和试验后似乎这些功能在Nashorn中还不是还。 快速搜索将告诉您我们不是唯一寻求解决方案的人。 通常会提到Rhino确实具有解决此问题的功能。 例如在JDK 8中Rhino javascript引擎具有“ instructionCount”回调机制而Nashorn中不存在该回调机制。 它基本上为您提供了一种在回叫中执行逻辑的方法该回叫会自动被每x条指令 字节码指令调用。 我首先尝试并且浪费了很多时间用Nashorn模仿指令计数的想法例如首先美化脚本因为人们可以将整个脚本写在一行上然后在脚本中注入一行代码来触发回调。 但是那是1做起来不是很简单2一个人仍然可以在无限运行/使用大量内存的一行上写一条指令。 搜索被困在那里导致我们找到了Mozilla的Rhino引擎 。 自从很久以前将其包含在JDK中以来它实际上就已经进一步发展了而JDK中的版本并未进行这些更改 阅读了非常稀疏的Rhino文档后很明显Rhino在我们的用例方面似乎具有更丰富的功能。 Nashorn的ClassFilter与Rhino中的“ ClassShutter”概念匹配。 使用Rhino的回调机制解决了cpu和内存问题您可以定义一个称为x指令的回调。 这意味着一行可能是数百个字节代码指令并且每x条指令我们都会得到一个回调……。 在执行脚本时它非常适合监视我们的cpu和内存使用情况。 如果您对我们在代码中实现这些想法感兴趣 请在此处查看 。 这确实意味着无论您使用什么JDK版本都不会使用嵌入式javascript引擎而会一直使用Rhino。 尝试一下 要使用新的安全脚本功能请添加以下依赖关系 dependencygroupIdorg.activiti/groupIdartifactIdactiviti-secure-javascript/artifactIdversion5.21.0/version
/dependency 这将暂时包括Rhino引擎。 这还将启用SecureJavascriptConfigurator 在创建流程引擎之前需要对其进行配置 SecureJavascriptConfigurator configurator new SecureJavascriptConfigurator().setWhiteListedClasses(new HashSetString(Arrays.asList(java.util.ArrayList))).setMaxStackDepth(10).setMaxScriptExecutionTime(3000L).setMaxMemoryUsed(3145728L).setNrOfInstructionsBeforeStateCheckCallback(10);ProcessEngine processEngine new StandaloneInMemProcessEngineConfiguration().addConfigurator(configurator).buildProcessEngine(); 这会将安全脚本配置为 每10条指令检查一次CPU执行时间和内存使用情况 给脚本3秒3MB的执行时间 将堆栈深度限制为10以避免递归 将数组列表公开为可以在脚本中安全使用的类 从上方运行试图读取ifconfig并关闭JVM的脚本会导致 TypeError无法在对象[JavaPackage java.lang.Runtime]中调用属性getRuntime。 它不是功能而是“对象”。 从上面运行无限循环脚本可以得出 线程“ main” java.lang.Error中的异常最大variableScope时间超过了3000 ms 从上面运行内存使用脚本可以 线程“主” java.lang.Error中的异常内存限制达到3145728字节 和欢呼 解决了上面定义的问题 性能 我做了一个非常不科学的快速检查……我几乎不敢分享它因为结果违背了我的设想。 我创建了一个快速主程序该主程序运行带有脚本任务的流程实例10000次 public class PerformanceUnsecure {public static void main (String[] args) {ProcessEngine processEngine new StandaloneInMemProcessEngineConfiguration().buildProcessEngine();RepositoryService repositoryService processEngine.getRepositoryService();repositoryService.createDeployment().addClasspathResource(performance.bpmn20.xml).deploy();Random random new Random();RuntimeService runtimeService processEngine.getRuntimeService();int nrOfRuns 10000;long total 0;for (int i0; inrOfRuns; i) {MapString, Object variables new HashMapString, Object();variables.put(a, random.nextInt());variables.put(b, random.nextInt());long start System.currentTimeMillis();runtimeService.startProcessInstanceByKey(myProcess, variables);long end System.currentTimeMillis();total (end - start);}System.out.println(Finished process instances : processEngine.getHistoryService().createHistoricProcessInstanceQuery().count());System.out.println(Total time total ms);System.out.println(Avg time/process instance ((double)total/(double)nrOfRuns) ms);}} 流程定义只是一个开始-脚本任务-结束。 脚本任务只是将变量添加到变量中然后将结果保存在第三个变量中。 scriptTask idmyScriptTask scriptFormatjavascriptscriptvar c a b;execution.setVariable(c, c);/script
/scriptTask 我运行了五次平均每个流程实例为2.57毫秒。 这是在最近的JDK 8所以是Nashorn上。 然后我切换了上面的前两行以使用新的安全脚本从而切换到Rhino并启用了安全功能 SecureJavascriptConfigurator configurator new SecureJavascriptConfigurator().addWhiteListedClass(org.activiti.engine.impl.persistence.entity.ExecutionEntity).setMaxStackDepth(10).setMaxScriptExecutionTime(3000L).setMaxMemoryUsed(3145728L).setNrOfInstructionsBeforeStateCheckCallback(1);ProcessEngine processEngine new StandaloneInMemProcessEngineConfiguration().addConfigurator(configurator).buildProcessEngine(); 再次进行了五次运行……并获得了1.07毫秒/流程实例。 这是同一件事的两倍以上 。 当然这不是一个真正的考验。 我以类白名单检查和回调为前提假设Rhino的执行速度会变慢但是没有这种事情。 也许这种特殊情况更适合Rhino……如果有人可以解释请发表评论。 但这仍然是一个有趣的结果。 结论 如果您在流程定义中使用脚本请仔细阅读引擎中的此新安全脚本功能。 由于这是一项新功能非常欢迎反馈和改进 翻译自: https://www.javacodegeeks.com/2016/06/secure-scripting-activiti-works.html