You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 14:54:58