snackjson v4.0.58 已经发布。这个版本的重点不是增加一个新的查询语法,而是为 JsonPath 处理链路加入 TypeChecker 类型安全检测机制,让应用可以在运行时限制哪些 Java 类型能够参与 JSON 反序列化或相关类型转换。
对于需要处理外部 JSON、插件数据或多租户输入的服务来说,JsonPath 的便利性不能替代边界控制。TypeChecker 提供了一个可以集中配置的检查点:允许可信包中的类型继续处理,拒绝其他来源的类型。
为什么 JsonPath 需要类型检查
JsonPath 通常用于从 JSON 文档中定位节点,例如读取用户信息、订单明细或配置项。实际应用中,读取路径只是问题的一部分,框架还可能根据调用者指定的 Java 类型执行转换。
当输入来自 HTTP 请求、消息队列或第三方文件时,类型转换就应当具备明确的安全策略。应用可以根据包名、类名或更细粒度的规则判断目标类型是否可信:
- 业务 DTO 可以放行;
- 内部实现类、测试类或未知包可以拒绝;
- 规则应集中配置,避免散落在每个调用点;
- 默认策略应偏向最小权限,而不是无条件接受。
v4.0.58 提供的 TypeChecker 正适合承担这层职责。摘要中的示例通过类名判断包名前缀:com.demo 下的类型允许处理,其他类型拒绝处理。
一个可改造的配置示例
下面是一个基于发布摘要中 API 形式整理的 Java 配置示例。请根据项目中实际使用的 Options 创建方式和依赖坐标进行调整:
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.TypeChecker;
public final class JsonSecurityConfig {
private JsonSecurityConfig() {
}
public static JSONReader.Feature[] trustedTypeFeatures() {
TypeChecker checker = clzName -> {
if (clzName == null) {
return TypeChecker.DENY;
}
// 只允许应用自己的 DTO 包参与类型处理。
if (clzName.startsWith("com.demo.dto.")) {
return TypeChecker.ALLOW;
}
return TypeChecker.DENY;
};
// 按当前 snackjson 版本的 Options API 注册 checker。
Options options = new Options();
options.addChecker(checker);
return options.features();
}
public static void main(String[] args) {
String json = "{\"name\":\"Ada\"}";
UserDto user = JSON.parseObject(
json,
UserDto.class,
trustedTypeFeatures()
);
System.out.println(user.name());
}
public record UserDto(String name) {
}
}
示例中的 Options 只是用于表达配置位置的占位类型。如果当前项目的 snackjson 版本提供了不同的 Options 包名或初始化方式,应以项目 API 为准。关键点在于把 TypeChecker 注册到统一的解析配置中,而不是在业务代码里重复判断类名。
如果项目只允许一个明确的 DTO 包,规则应当尽量写成带结尾点号的前缀,例如 com.demo.dto.,而不是宽泛的 com.demo。这样可以避免误放行 com.demoevil 之类的相似包名。
规则设计时要注意什么
默认拒绝未知类型
对来自外部边界的数据,未知类名不应该自动获得权限。可以采用如下策略:
options.addChecker(clzName -> {
if (clzName == null) {
return TypeChecker.DENY;
}
return clzName.startsWith("com.demo.dto.")
? TypeChecker.ALLOW
: TypeChecker.DENY;
});
如果业务确实需要支持多个包,建议显式列出允许范围,并为每个范围写测试,而不是直接返回 ALLOW。
把检查器当作安全边界
TypeChecker 能限制类型处理范围,但它不是完整的输入校验方案。生产服务仍应同时完成:
- JSON 字段和长度限制;
- 业务字段校验;
- 请求来源和权限认证;
- 反序列化异常处理;
- 对拒绝类型和异常输入进行日志记录,但避免记录敏感数据。
类型检查解决的是“允许处理哪些类型”,并不等价于“输入内容一定合法”。
为规则建立回归测试
至少覆盖允许类型、拒绝类型、空类名和相似包名四类场景:
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
class TypeCheckerTest {
@Test
void onlyTrustedDtoPackageIsAllowed() {
TypeChecker checker = clzName -> {
if (clzName == null) {
return TypeChecker.DENY;
}
return clzName.startsWith("com.demo.dto.")
? TypeChecker.ALLOW
: TypeChecker.DENY;
};
assertEquals(TypeChecker.ALLOW,
checker.check("com.demo.dto.UserDto"));
assertEquals(TypeChecker.DENY,
checker.check("com.demo.internal.UserMapper"));
assertEquals(TypeChecker.DENY,
checker.check("com.demoevil.dto.UserDto"));
assertEquals(TypeChecker.DENY, checker.check(null));
}
}
具体方法名可能因当前 API 定义而不同;测试表达的重点是规则本身,而不是把判断逻辑隐藏在框架调用中。
升级建议
升级到 v4.0.58 时,可以按这个顺序检查:
- 盘点项目中所有需要类型转换的 JsonPath 或 JSON 解析入口。
- 为可信 DTO 建立明确的包边界。
- 使用
TypeChecker配置允许列表,默认拒绝未知类型。 - 对合法和非法类型分别编写测试。
- 在预发布环境观察拒绝日志和解析异常,再逐步扩大允许范围。
TypeChecker 的价值在于提供了一个集中、可审计的类型控制点。它不会替代权限、校验和异常处理,但可以让 JsonPath 的灵活性拥有更清晰的安全边界。对于处理不可信 JSON 的服务,升级时把这项能力纳入统一解析配置,通常比在各个业务方法中临时补规则更容易长期维护。