activiti脚本任务

最近的Activiti 5.21.0版本的突出特点之一是“安全脚本”。 Activiti用户指南中详细介绍了启用和使用此功能的方法 。 在这篇文章中,我将向您展示我们如何实现其最终实现以及它在幕后所做的事情。 当然,因为这是我通常的签名风格,所以我们还将对性能进行一些了解。

问题

长期以来,Activiti引擎一直支持脚本任务(和任务/执行侦听器)的脚本编写。 所使用的脚本在流程定义中定义,并且可以在部署流程定义后直接执行。 这是很多人喜欢的东西。 这与Java委托类或委托表达式有很大的不同,因为它们通常需要将实际的逻辑放在类路径上。 它本身已经引入了某种“保护”,因为高级用户通常只能这样做。

但是,使用脚本不需要这种“额外步骤”。 如果您将脚本任务的功能提供给最终用户(并且我们从某些用户那里知道某些公司确实拥有此用例),那么所有的赌注都将大打折扣。 您可以通过执行流程实例来关闭JVM或执行恶意操作。

第二个问题是编写一个无限循环且永无止境的脚本非常容易。 第三个问题是,脚本在执行时可以轻松使用大量内存,并占用大量系统资源。

让我们来看入门的第一个问题。 首先,让我们添加最新和最大的Activiti引擎依赖性以及内存数据库库中的H2:

<dependencies><dependency><groupId>org.activiti</groupId><artifactId>activiti-engine</artifactId><version>5.21.0</version></dependency><dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><version>1.3.176</version></dependency>
</dependencies>

我们将在这里使用的过程非常简单:只是一个开始事件,脚本任务和结束。 此处的过程并不是真正的重点,而是脚本执行。

我们将尝试的第一个脚本有两件事:它将获取并显示我的机器的当前网络配置(但显然有此想法的更危险的应用程序), 然后关闭整个JVM 。 当然,在适当的设置中,可以通过确保运行逻辑的用户在计算机上没有任何重要权限(但不能解决占用资源的问题)来缓解其中的某些问题。 但是我认为这很好地说明了为什么向几乎任何人提供脚本的功能在安全性方面确实很糟糕。

<scriptTask id="myScriptTask" scriptFormat="javascript"><script>var 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.RuntimeException:java.lang.ClassNotFoundException:java.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。

尝试一下

要使用新的安全脚本功能,请添加以下依赖关系:

<dependency><groupId>org.activiti</groupId><artifactId>activiti-secure-javascript</artifactId><version>5.21.0</version>
</dependency>

这将暂时包括Rhino引擎。 这还将启用SecureJavascriptConfigurator ,在创建流程引擎之前需要对其进行配置:

SecureJavascriptConfigurator configurator = new SecureJavascriptConfigurator().setWhiteListedClasses(new HashSet<String>(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。 它不是功能,而是“对象”。

从上面运行无限循环脚本可以得到

线程“主” 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 i=0; i<nrOfRuns; i++) {Map<String, Object> variables = new HashMap<String, 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 id="myScriptTask" scriptFormat="javascript"><script>var 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

activiti脚本任务

activiti脚本任务_Activiti中的安全脚本如何工作相关推荐

  1. activiti脚本任务_Activiti中的高级脚本:自定义配置注入

    activiti脚本任务 脚本任务可能是Activiti代码库中"最古老的"类之一,但是我认为它仍然未被许多人使用. (感知到的)缺点当然是性能(解释还是编译),并且从IDE角度来 ...

  2. 【Groovy】Groovy 脚本调用 ( Linux 中调用 Groovy 脚本 | Windows 中调用 Groovy 脚本 )

    文章目录 前言 一.Linux 中调用 Groovy 脚本 二.Windows 中调用 Groovy 脚本 前言 在 命令行 , Groovy 脚本 , Groovy 类 , Java 类中 , 可以 ...

  3. aix shell脚本 运行java_Linux中执行shell脚本的4种方法总结

    Linux中执行shell脚本的4种方法总结,即在Linux中运行shell脚本的4种方法: 方法一:切换到shell脚本所在的目录(此时,称为工作目录)执行shell脚本: 复制代码 代码如下: c ...

  4. linux logstash启动脚本,在Linux中从bash脚本启动时Logstash shutdown停止

    我写了一个bash脚本,它在指定的文件夹中找到CSV文件,并使用正确的配置文件将它们管道到logstash中.但是,当运行此脚本时,我遇到以下错误,说关闭进程停止,导致无限循环,直到我用ctrl c手 ...

  5. linux一键安装脚本编写,Linux中自动安装脚本

    如何让你的主机自动的完成安装,自动的回答系统提出的问题,编写自动安装脚本即可实现,如下将详细解释自动脚本安装方法. systemd-config-kickstart  ##自动应答脚本制作工具 安装成 ...

  6. activiti 多租户_Activiti中具有独立数据库架构的多租户

    activiti 多租户 我们过去听到的一项功能请求是以多租户方式运行Activiti引擎,使租户的数据与其他数据隔离. 当然,在某些云/ SaaS环境中,这是必须的. 几个月前,波恩大学的学生拉斐尔 ...

  7. lua脚本php,phpStudy中起用lua脚本_PHP教程

    phpStudy集成了多种web服务的综合配置,如经典的apache+mysql.nginx+mysql等,在使用apache+mysql时,希望能够使用lua脚本,于是利用配置选项,启用lua模块, ...

  8. arcgis的python脚本-在ArcGIS中写python脚本读取shapefile

    ArcGIS帮助中的相关代码并不能直接运行,进行了小幅修改,现在就可以直接运行了. python代码的大致思路是: (1)先获得描述shp的相关信息,获得存储图形信息的字段名: (2)然后获得游标: ...

  9. postek二次开发_详解Visual Basic脚本在BarTender中的应用

    Visual Basic脚本可在常规打印时操作中使用,或与打印机代码修改器操作一同使用.BarTender 中的脚本助理包含一个脚本编写区域,在该区域中,既可以通过键入方式编写实际脚本,也可以使用脚本 ...

最新文章

  1. java方法定义 与调用_Java方法的定义及调用
  2. 移动时代的互联网格局变化
  3. 34. Find First and Last Position of Element in Sorted Array
  4. Swift UI开发初探
  5. linux内核实现片选跳变,《Linux内核设计与实现》读书笔记:进程调度
  6. JSPstudy恢复80端口
  7. java查询时加上进度条插件,简单的jquery进度条插件LineProgressbar.js
  8. 易语言高级表格写入MYSQL_易语言高级表格读写EXCEL源码
  9. 各大卷王轮番上阵,“三无”计算机保研er如何逆袭?
  10. mysql中分层查询_在mysql中连接任意数量的字符串行(分层查询)
  11. 中国苹果之都苹果快成熟了
  12. 测试用例(功能用例)——资产盘点
  13. STM32 USB HID设置(STM32CubeMX)
  14. 坐标变换 及矩阵变换
  15. storm和vgj vgj_Team VGJ发布公告解散 两年征程划上句号
  16. 关注龙之梦和长峰地产
  17. Codeforces Round #127 (Div. 1) B. Guess That Car! 扫描线
  18. Ariel India旨在宣扬共同承担家务的新影片《See Equal》引发热烈反响
  19. Android ContentProvider之联系人数据库及操作
  20. 代码学习网站FreeCodeCamp

热门文章

  1. 杯子 + Kronican
  2. P2601 [ZJOI2009]对称的正方形(二维哈希)(二分)
  3. P5662-纪念品【dp】
  4. jzoj3890-长途旅行【同余最短路】
  5. jzoj6342-[NOIP2019模拟2019.9.7]Tiny Counting【树状数组,容斥】
  6. 【区间DP】摆渡线路(2017 特长生 T4)
  7. Sentinel(十一)之黑白名单控制
  8. 图说:为什么Java中的字符串被定义为不可变的
  9. Junit5新功能一览
  10. com.microsoft.sqlserver.jdbc.SQLServerException: 索引 7 超出范围。