Solon v4.0.4

snack4 - Json 序列化的安全控制

</> markdown
2026年8月3日 下午3:06:56

序列化类型安全控制,对日常的应用开发尤其重要。(涉及序列化的框架,都会涉及安全问题)

情况1:默认只访问字段(避免触发行为,比较安全)

默认是这个状态:

  • 不允许使用 setter, getter
  • 不允许使用 有参数的构造方法(但允许 record 或全部只读字段的类使用)

java17 后,模块化的类可能会有访问权限问题。(可通过 --add-opens 开放模块)

情况2:默认状态下启用 Feature.Read_AutoType (比较安全)

涉及特性(一般不需要启用):

Feature.Read_AutoType

如有必要再添加"动态类"(根据字符串加载类)的安全检测(已内置简单的 TypeSafelist):

options.addChecker(clzName -> {
    //通过
    if (clzName.startsWith("com.ok.xxx.")) {
        return TypeChecker.ALLOW;
    }
    
    //拒绝
    if (clzName.startsWith("com.no.yyy.")) {
        return TypeChecker.DENY;
    }
    
    //跳过
    return TypeChecker.SKIP;
});

情况3:只启用 setter, getter 和 parameterized constructor(比较安全)

启用这三个特性后,模块化类的有访问权限问题可以启用。涉及特性:

Feature.Encode_OnlyUseGetter //序列化时只通过 getter 读字段(旧 Read_OnlyUseGetter 已废弃)
Feature.Decode_OnlyUseSetter //反序列化时只通过 setter 写字段(旧 Write_OnlyUseSetter 已废弃)
Feature.Write_AllowParameterizedConstructor

情况4:(情况2 + 情况3)需要加黑白名单机制(不安全)

(比较)如果要序列化 Exception 类,需要启用 情况2 + 情况3 的多种特性,会比较不安全。需要加黑白名单机制

//序列化
String json = ONode.ofBean(e,
        Feature.Write_ClassName, //write json
        Feature.Encode_OnlyUseGetter //read bean
).toJson();

//反序列化
NullPointerException e = ONode.ofJson(json,
        Feature.Decode_OnlyUseSetter,
        Feature.Write_AllowParameterizedConstructor,
        Feature.Read_AutoType
).toBean();

白黑名单结合参考:

public class TypeSafety {
    @Test
    public void case1() {
        Options options = Options.of();
        
        //使用 TypeChecker 做动态类的安全检测(主要为 Feature.Read_AutoType 提供安全帮助)
        options.addChecker(clzName -> {
            if (clzName.startsWith("com.example")) {
                return TypeChecker.ALLOW;
            }
            return TypeChecker.DENY;
        });
        
        //使用 ObjectFactory 模拟已加载类的白名单(可选)
        options.addCreator(User.class, (opts, node, clazz) -> new User());
        options.addCreator(Order.class, (opts, node, clazz) -> new Order());

        //使用 ObjectPatternFactory 模拟已加载类的黑名单(必选)
        options.addCreator(new ObjectPatternFactory<Object>() {
            @Override
            public boolean calCreate(Class<?> clazz) {
                return true;
            }

            @Override
            public Object create(Options opts, ONode node, Class<?> clazz) {
                if(Throwable.class.isAssignableFrom(clazz) == false) { //其它类,只以允许 Throwable
                    throw new SnackException("");
                } else {
                    return null; //交给框架自动处理
                }
            }
        });

        //效果测试
        Assertions.assertThrows(SnackException.class, () -> {
            ONode.deserialize("{id:1}", UserModel.class, options);
        });

        ONode.deserialize("{id:1}", Map.class, options);
    }
}