栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java集成groovy脚本实现缓存(解决死循环、不安全方法问题)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java集成groovy脚本实现缓存(解决死循环、不安全方法问题)

问题:
  • groovy脚本中while退出判断条件错误,线程陷入死循环
  • 未对System.exit和Runtime.getRuntime.exec方法做限制
maven依赖:

    org.codehaus.groovy
    groovy
    3.0.9


    org.craftercms
    groovy-sandbox
    1.27.6

代码实现:
import groovy.lang.Binding;
import groovy.lang.GroovyShell;
import groovy.lang.script;
import groovy.transform.ConditionalInterrupt;
import groovy.transform.ThreadInterrupt;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.expr.Classexpression;
import org.codehaus.groovy.ast.expr.Closureexpression;
import org.codehaus.groovy.ast.expr.Constantexpression;
import org.codehaus.groovy.ast.expr.MethodCallexpression;
import org.codehaus.groovy.ast.stmt.expressionStatement;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer;
import org.junit.Test;
import org.kohsuke.groovy.sandbox.GroovyInterceptor;
import org.kohsuke.groovy.sandbox.SandboxTransformer;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class GroovyShellTest {
    private static final Map script_CACHE = new ConcurrentHashMap<>();
    @Test
    public void scriptTest(){
        // 脚本名称,key
        String scriptId = "test";
        // groovy脚本,value
        String scriptext = "def o = new Object();ndef t = o.class";
        script script = script_CACHE.get(scriptId);
        if (script == null) {
            // 缓存脚本,提高执行性能
            GroovyShell shell = createGroovyShell();
            script = shell.parse(scriptext);
            script_CACHE.put(scriptId, script);
        }
        // 变量绑定,自己看情况加
        Binding binding = doBinding(scriptext, new HashMap());
        script.setBinding(binding);
        // 重置调用时间
        ThreadLocalUtils.setStartTime();
        // 添加脚本名称
        ThreadLocalUtils.set("scriptName", scriptId);
        Object result = script.run();
        System.err.println(result);
    }

    
    private GroovyShell createGroovyShell() {
        // 自定义配置
        CompilerConfiguration config = new CompilerConfiguration();

        // 保存生成的class文件
//        config.setTargetDirectory("./");
//        System.err.println(config.getTargetDirectory().getAbsolutePath());

        // 添加线程中断拦截器,可拦截循环体(for,while)、方法和闭包的首指令
        config.addCompilationCustomizers(new ASTTransformationCustomizer(ThreadInterrupt.class));

        // 添加线程中断拦截器,可中断超时线程
        // TimedInterrupt是线程监控的,不适合缓存脚本超时判断
//         Map timeoutArgs = ImmutableMap.of("value", 300,
//                "checkOnMethodStart", false);
//         config.addCompilationCustomizers(new ASTTransformationCustomizer(timeoutArgs, TimedInterrupt.class));
        Map timeoutArgs = new HashMap<>();
        timeoutArgs.put("value", new Closureexpression(
                Parameter.EMPTY_ARRAY,
                new expressionStatement(
                        new MethodCallexpression(
                                new Classexpression(ClassHelper.make(ConditionInterceptor.class)),
                                "checkTimeout",
                                new Constantexpression(10))
                )
        ));
        config.addCompilationCustomizers(new ASTTransformationCustomizer(timeoutArgs, ConditionalInterrupt.class));

        // 沙盒环境
        config.addCompilationCustomizers(new SandboxTransformer());
        GroovyShell shell = new GroovyShell(config);
        // 注册方法拦截
        new GroovyNotSupportInterceptor().register();

        return shell;
    }

    
    private Binding doBinding(String scriptText, Object params) {
        Binding binding = new Binding();
        binding.setVariable("utils", new Object());
        return binding;
    }
}


class ConditionInterceptor {

    
    static boolean checkTimeout(int timeout) {
        boolean flag = ThreadLocalUtils.getStartTime() + timeout * 1000 < System.currentTimeMillis();
        if (flag) {
            System.err.printf("[%s] Execution timed out after %s seconds. Start Time:%s%n",
                    ThreadLocalUtils.get("scriptName"),
                    timeout,
                    LocalDateTime.ofInstant(Instant.ofEpochMilli(ThreadLocalUtils.getStartTime()), ZoneOffset.ofHours(8))
                            .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        }
        return flag;
    }
}


class ThreadLocalUtils {
    // 存储脚本开始执行时间
    private static final ThreadLocal> THREAD_LOCAL = ThreadLocal.withInitial(HashMap::new);

    private ThreadLocalUtils() {
    }

    public static long getStartTime() {
        return (long) THREAD_LOCAL.get().get("__startTime");
    }

    public static void setStartTime() {
        THREAD_LOCAL.get().put("__startTime", System.currentTimeMillis());
    }

    public static void set(String key, Object value) {
        THREAD_LOCAL.get().put(key, value);
    }

    public static Object get(String key) {
        Map map = THREAD_LOCAL.get();
        if (Objects.isNull(map)) {
            return null;
        }
        return map.get(key);
    }

    public static void setAll(Map map) {
        THREAD_LOCAL.get().putAll(map);
    }
}

class GroovyNotSupportInterceptor extends GroovyInterceptor {

    public static final List defaultMethodBlacklist = Arrays.asList("getClass", "class", "wait", "notify", "notifyAll", "invokeMethod", "finalize");

    
    @Override
    public Object onStaticCall(GroovyInterceptor.Invoker invoker, Class receiver, String method, Object... args) throws Throwable {
        if (receiver == System.class && "exit".equals(method)) {
            // System.exit(0)
            throw new SecurityException("No call on System.exit() please");
        } else if (receiver == Runtime.class) {
            // 通过Java的Runtime.getRuntime().exec()方法执行shell, 操作服务器…
            throw new SecurityException("No call on RunTime please");
        } else if (receiver == Class.class && "forName".equals(method)) {
            // Class.forName
        }
        return super.onStaticCall(invoker, receiver, method, args);
    }

    
    @Override
    public Object onMethodCall(GroovyInterceptor.Invoker invoker, Object receiver, String method, Object... args) throws Throwable {
        if (defaultMethodBlacklist.contains(method)) {
            // 方法列表黑名单
            throw new SecurityException("Not support method: " + method);
        }
        return super.onMethodCall(invoker, receiver, method, args);
    }

    @Override
    public Object onGetProperty(Invoker invoker, Object receiver, String property) throws Throwable {
        if ("class".contains(property)) {
            throw new SecurityException("Not support clz.class");
        }
        return super.onGetProperty(invoker, receiver, property);
    }
}
Class文件

测试脚本:

while(true){
	Thread.sleep(1000)
}
System.exit(1)
return 1

class文件内容:

import groovy.lang.Binding;
import groovy.lang.script;
import org.codehaus.groovy.runtime.ArrayUtil;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.scriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;
import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
import org.kohsuke.groovy.sandbox.impl.Checker;

public class script1 extends script {
    public script1() {
        CallSite[] var1 = $getCallSiteArray();
        super();
    }

    public script1(Binding context) {
        CallSite[] var2 = $getCallSiteArray();
        super(context);
    }

    public static void main(String... args) {
        CallSite[] var1 = $getCallSiteArray();
        if (DefaultTypeTransformation.booleanUnbox(var1[0].callStatic(Checker.class, ArrayUtil.createArray(var1[1].callStatic(Checker.class, ArrayUtil.createArray(Thread.class, false, false, "currentThread", var1[2].call(scriptBytecodeAdapter.createList(new Object[0])))), false, false, "isInterrupted", var1[3].call(scriptBytecodeAdapter.createList(new Object[0])))))) {
            throw (Throwable) var1[4].callStatic(Checker.class, InterruptedException.class, var1[5].call(scriptBytecodeAdapter.createList(new Object[]{"Execution interrupted. The current thread has been interrupted."})));
        } else {
            var1[6].callStatic(Checker.class, InvokerHelper.class, "runscript", var1[7].call(scriptBytecodeAdapter.createList(new Object[]{script1.class, args})));
        }
    }

    public Object run() {
        CallSite[] var1 = $getCallSiteArray();
        if (DefaultTypeTransformation.booleanUnbox(var1[8].callStatic(Checker.class, ArrayUtil.createArray(var1[9].callStatic(Checker.class, ArrayUtil.createArray(Thread.class, false, false, "currentThread", var1[10].call(scriptBytecodeAdapter.createList(new Object[0])))), false, false, "isInterrupted", var1[11].call(scriptBytecodeAdapter.createList(new Object[0])))))) {
            throw (Throwable) var1[12].callStatic(Checker.class, InterruptedException.class, var1[13].call(scriptBytecodeAdapter.createList(new Object[]{"Execution interrupted. The current thread has been interrupted."})));
        } else {
            while (!DefaultTypeTransformation.booleanUnbox(var1[14].callStatic(Checker.class, ArrayUtil.createArray(this, false, false, "conditionalTransform550147359$condition", var1[15].call(scriptBytecodeAdapter.createList(new Object[0])))))) {
                if (DefaultTypeTransformation.booleanUnbox(var1[18].callStatic(Checker.class, ArrayUtil.createArray(var1[19].callStatic(Checker.class, ArrayUtil.createArray(Thread.class, false, false, "currentThread", var1[20].call(scriptBytecodeAdapter.createList(new Object[0])))), false, false, "isInterrupted", var1[21].call(scriptBytecodeAdapter.createList(new Object[0])))))) {
                    throw (Throwable) var1[22].callStatic(Checker.class, InterruptedException.class, var1[23].call(scriptBytecodeAdapter.createList(new Object[]{"Execution interrupted. The current thread has been interrupted."})));
                }

                var1[24].callStatic(Checker.class, ArrayUtil.createArray(Thread.class, false, false, "sleep", var1[25].call(scriptBytecodeAdapter.createList(new Object[]{1000}))));
            }

            throw (Throwable) var1[16].callStatic(Checker.class, InterruptedException.class, var1[17].call(scriptBytecodeAdapter.createList(new Object[]{"Execution interrupted. The following condition failed: Error calculating source code for expression. Trying to read line 0 from class org.codehaus.groovy.control.io.StringReaderSource"})));
        }
    }
}

有点长…主要信息在run方法中。有一个线程中断判断(ThreadInterrupt),有一个自定义条件判断(while中ConditionalInterrupt)。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/337369.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号