SaaS产品中SpEL安全使用咨询:Standard与SimpleEvaluationContext选型
SpEL安全使用与函数扩展问题解答
1. 安全使用StandardEvaluationContext的方案
可以通过限制可访问的类、方法,禁用危险能力来降低注入风险,具体实现方式如下:
(1)限制可访问的Java类型
自定义TypeLocator,仅允许加载安全的核心类型(如String、Integer、Boolean等),拒绝加载java.lang.Runtime、java.rmi相关等危险类:
class RestrictedTypeLocator implements TypeLocator { private final Set<String> allowedTypes = Set.of( "java.lang.String", "java.lang.Integer", "java.lang.Boolean", "java.util.regex.Pattern" ); @Override public Class<?> findType(String typeName) throws EvaluationException { if (!allowedTypes.contains(typeName)) { throw new EvaluationException("Type " + typeName + " is not allowed"); } try { return Class.forName(typeName); } catch (ClassNotFoundException e) { throw new EvaluationException(e); } } }
(2)限制可调用的方法
通过StandardEvaluationContext的setMethodFilter,仅允许调用安全的方法(如String.toUpperCase()、String.matches()等):
StandardEvaluationContext context = new StandardEvaluationContext(); // 设置自定义TypeLocator context.setTypeLocator(new RestrictedTypeLocator()); // 限制方法调用 context.setMethodFilter((method, target) -> { // 仅允许String类的指定安全方法 if (target instanceof String) { return Set.of("toUpperCase", "toLowerCase", "matches", "length").contains(method.getName()); } // 其他类型仅允许无风险的方法,或直接拒绝 return false; }); // 禁用BeanResolver,防止访问Spring容器Bean context.setBeanResolver(null); // 设置变量 context.setVariable("Tag", "57345"); context.setVariable("Name", "defg");
(3)额外安全措施
- 禁用表达式中的构造器调用:通过设置
ConstructorResolver为null,防止用户实例化危险类。 - 对用户输入的表达式做前置校验:比如禁止出现
new、java.lang.Runtime、rmi等关键词,提前拦截风险表达式。
2. SimpleEvaluationContext实现标准/自定义函数调用
SimpleEvaluationContext默认是受限模式,但可以通过注册函数、配置访问规则来支持所需功能:
(1)注册自定义函数
使用SimpleEvaluationContext.Builder的registerFunction方法,将自定义方法或标准Java方法注册为可调用的函数:
// 自定义工具类,包装标准方法或自定义逻辑 class ExpressionFunctions { // 包装String.toUpperCase()为静态函数 public static String toUpperCase(String input) { return input.toUpperCase(); } // 自定义匹配函数 public static boolean matches(String input, String regex) { return input.matches(regex); } } // 构建SimpleEvaluationContext并注册函数 SimpleEvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding() .withFunctions() .registerFunction("toUpperCase", ExpressionFunctions.class.getMethod("toUpperCase", String.class)) .registerFunction("matches", ExpressionFunctions.class.getMethod("matches", String.class, String.class)) .build(); // 设置变量 context.setVariable("Tag", "57345"); context.setVariable("Name", "defg"); // 表达式写法调整为调用注册的函数 Expression exp = parser.parseExpression("(matches(#Tag, '52.*') && matches(toUpperCase(#Name), 'DEF.*'))"); Boolean result = (Boolean) exp.getValue(context);
(2)支持实例方法调用(可选)
如果需要直接调用对象的实例方法,可以通过添加PropertyAccessor来扩展SimpleEvaluationContext的能力,比如支持String的常用方法:
class StringPropertyAccessor extends ReflectivePropertyAccessor { @Override public boolean canRead(EvaluationContext context, Object target, String name) throws EvaluationException { return target instanceof String && Set.of("toUpperCase", "toLowerCase").contains(name); } @Override public TypedValue read(EvaluationContext context, Object target, String name) throws EvaluationException { if (target instanceof String str) { switch (name) { case "toUpperCase": return new TypedValue(str.toUpperCase()); case "toLowerCase": return new TypedValue(str.toLowerCase()); default: throw new EvaluationException("Method not allowed"); } } return super.read(context, target, name); } } // 构建时添加自定义PropertyAccessor SimpleEvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding() .withPropertyAccessors(new StringPropertyAccessor()) .build(); // 表达式可以直接调用实例方法 Expression exp = parser.parseExpression("(#Tag.matches('52.*') && #Name.toUpperCase().matches('DEF.*'))"); Boolean result = (Boolean) exp.getValue(context);
内容的提问来源于stack exchange,提问作者AdityaK01
相关产品推荐
相关产品推荐

