安全 || 为了安全我升级到了 fastjson2
一、前言
小伙伴大家好,我是如梦技术春鸽(哥),mica 系列开源作者。
早些天,温少在开源软件作者群放出了 《fastjson2 的更新动态》和 《fastjson2 中提升 Java 字符串编码解码性能的技巧》等相关文章,笔者就开始关注 fastjson2。
加上前2天 fastjson 再报安全漏洞,笔者研究了一下 fastjson2,暂且不说 fastjson2 的性能提升,在安全上值得我们升级。
二、罪魁祸首 AutoType
fastjson、jackson 都支持 AutoType 功能,这个功能在序列化的 JSON 字符串中带上类型信息,在反序列化时,不需要传入类型,实现自动类型识别。
三、fastjson1 安全问题
fastjson 1.x 内部维护了一个白名单,java 发展近 30 年难免有些漏网之鱼,这也造成近几年 fastjson 安全漏洞频发,养活了一大票安全人员。
四、jackson AutoType 处理
同样,jackson 也支持 AutoType,jackson 爆出的几次安全漏洞也跟它有关。Jackson 的 json 解析跟 ObjectMapper 绑定,我们可以根据不同的需求来定制 ObjectMapper。
例如:只在 redis 序列化和反序列时激活 DefaultTyping。(示例代码,mica-redis 中采用 ObjectMapper copy 生成新的 ObjectMapper bean 避免污染全局 ObjectMapper 造成不安全)
public RedisSerializer{
// jackson findAndRegisterModules,use copy
ObjectMapper objectMapper = objectProvider.getIfAvailable(ObjectMapper::new).copy();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
// findAndRegisterModules
objectMapper.findAndRegisterModules();
// class type info to json
GenericJackson2JsonRedisSerializer.registerNullValueSerializer(objectMapper, null);
objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), DefaultTyping.NON_FINAL, As.PROPERTY);
return new GenericJackson2JsonRedisSerializer(objectMapper);
}
五、fastjson2 的设计
fastjson2 AutoType 必须显示打开才能使用,没有任何白名单,也不包括任何 Exception 类的白名单。这可以保证缺省配置下是安全的。
序列化带上类型信息
序列化是带上类型信息,需要使用 JSONWriter.Feature.WriteClassName。比如:
Bean bean = ...;
String jsonString = JSON.toJSONString(bean, JSONWriter.Feature.WriteClassName);
很多时候,root对象是可以知道类型的,里面的对象字段是基类或者不确定类型,这个时候不输出root对象的类型信息,可以减少序列化结果的大小,也能提升反序列化的性能。
Bean bean = ...;
String jsonString = JSON.toJSONString(bean, JSONWriter.Feature.WriteClassName, JSONWriter.Feature.NotWriteRootClassName);
反序列化打开AutoType功能支持自动类型
Bean bean = (Bean) JSON.parseObject(jsonString, Object.class, JSONReader.Feature.SupportAutoType);
fastjson2 AutoType 支持配置 safeMode,在 safeMode 打开后,显式传入 AutoType 参数也不起作用,具体配置如下:
-Dfastjson2.parser.safeMode=true
fastjson2 AutoType 会经过内置黑名单过滤。该黑名单能拦截大部分常见风险,这个机制不能保证绝对安全,打开 AutoType 不应该在暴露在公网的场景下使用。这点无可厚非,毕竟 AutoType 的场景不是在 web api,
六、升级到 fastjson2
6.1 兼容模式升级
升级可以通过兼容模式升级,兼容模式不需要改代码,但在深度使用的场景,不能做到完全兼容,通过这样的模式升级虽然省事,请认证测试,遇到问题反馈到 https://github.com/alibaba/fastjson2/issues
兼容模式Maven依赖
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>${fastjson2.version}version>
dependency>
6.2 使用新API升级
使用新API是建议的升级方式,使用新的API能获得更多的功能。
包名编程 FASTJSON
v2和1.x版本使用不同的package,新的package名称是com.alibaba.fastjson2,新package和之前不同,可以实现1.x和2.x共存
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONArray;
Maven依赖 Maven依赖的groupId和1.x不同,使用了新的groupId com.alibaba.fastjson2
<dependency>
<groupId>com.alibaba.fastjson2groupId>
<artifactId>fastjson2artifactId>
<version>${fastjson2.version}version>
dependency>
如果你需要用到spring支持的功能,还需要依赖fastjson2-extension
<dependency>
<groupId>com.alibaba.fastjson2groupId>
<artifactId>fastjson2-extensionartifactId>
<version>${fastjson2.version}version>
dependency>
笔者尝试升级的一个项目依赖比较少,并且也没有强制依赖 fastjson1,另外项目中也没用到一些奇淫技巧。所以选择了比较激进的直接升级新 API。
另外有使用 FastJsonHttpMessageConverter
也是更改一下包名即可。
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
更改为:
import com.alibaba.fastjson2.support.config.FastJsonConfig;
import com.alibaba.fastjson2.support.spring.http.converter.FastJsonHttpMessageConverter;
@Configuration(proxyBeanMethods = false)
public class FastJsonConfiguration {
@Bean
public FastJsonHttpMessageConverter fastJsonHttpMessageConverter() {
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
converter.setFastJsonConfig(new FastJsonConfig());
converter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON, new MediaType("application", "*+json")));
return converter;
}
}
最后
fastjson2 在性能和安全上都得到了很好的提升,特别是支持了 jsonb,在缓存、rpc 等场景优先考虑 jsonb。