import groovy.lang.GroovyClassLoader;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class GroovyClassLocaderConfig {
@Bean
public GroovyClassLoader groovyClassLoader( ) {
GroovyClassLoader classLoader = new GroovyClassLoader();
return classLoader;
}
}
import java.security.Permission;
public class GroovySecurityManager extends SecurityManager{
@Override
public void checkExit(int status) {
throw new SecurityException();
}
@Override
public void checkPermission(Permission permission) {
// 允许其他行为
}
}
import GroovyClassCacheUtils;
import groovy.lang.GroovyObject;
public class GroovyExector{
private static final String EXPRESS_SAFE_CHECK_PREVFIX = "import xxx.GroovySecurityManager;\n" +
"import groovy.transform.TimedInterrupt;\n" +
"import java.util.concurrent.TimeUnit;\n" +
"@TimedInterrupt(unit = TimeUnit.MILLISECONDS, value = 10000L)\n" +
"@groovy.transform.ThreadInterrupt\n" +
"def safeMethod( serviceBeans ){\n" +
// 运行其他『危险』指令之前执行
" GroovySecurityManager secManager = new GroovySecurityManager();\n" +
" System.setSecurityManager(secManager);\n" +
" bootMethod(serviceBeans );\n" +
"}\n";
public static Object execute( String express ) {
try {
String express_safe = EXPRESS_SAFE_CHECK_PREVFIX + express;
Class scriptClass = GroovyClassCacheUtils.getClassByGroovyScript( express_safe );
GroovyObject groovyInstance = (GroovyObject) scriptClass.newInstance();
try {
Object testResult = groovyInstance.invokeMethod("safeMethod","xxx");
if( testResult == null ){
throw new BusinessException( "groovy 脚本 bootMethod 方法缺少返回值" );
}
return testResult;
}catch ( SecurityException e ){
throw new BusinessException( "检测到恶意用户代码..." );
}
}catch ( Exception e ){
throw new BusinessException( e.getMessage() );
}
}
}
public class Test(){
public static void main(String[] args) {
String express = "def bootMethod( ){\n" +
" int i = 0;\n" +
" while( true ){\n" +
" System.out.println( i++ );\n" +
" Thread.sleep( 100L );\n" +
" }\n" +
" System.exit( 0 );\n" +
"}";
GroovyExector.execute( express );
}
}
import groovy.lang.GroovyClassLoader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class GroovyClassCacheUtils {
private static GroovyClassLoader groovyClassLoader;
@Autowired
public void setGroovyClassLoader( GroovyClassLoader groovyClassLoader ) {
GroovyClassCacheUtils.groovyClassLoader = groovyClassLoader;
}
/**
* 缓存 md5 和 groovy 脚本解析生成的 java class 的 map
*/
private static final Map<String,Class> GROOVY_CLASS_MAP = new HashMap<>();
/**
* 该方法可能在多线程环境下使用,所以要加锁
* @param groovyScript
*/
public static Class getClassByGroovyScript( String groovyScript ){
String md5 = MD5Utils.generateStringMD5(groovyScript);
Class scriptClass = GroovyClassCacheUtils.GROOVY_CLASS_MAP.get( md5 );
if( scriptClass == null ){
synchronized(GroovyClassCacheUtils.class){
if( scriptClass == null ){
scriptClass = GroovyClassCacheUtils.groovyClassLoader.parseClass( groovyScript );
GroovyClassCacheUtils.GROOVY_CLASS_MAP.put( md5,scriptClass );
}
}
}
return scriptClass;
}
}
ps:这里有一个坑,就是当 class GroovySecurityManager 定义在 groovy 脚本里面的话,使用 @TimedInterrupt(unit = TimeUnit.MILLISECONDS, value = 10000L) 和 @groovy.transform.ThreadInterrupt 注解,会发生 Stack Overflow