fastjson:我哭了,差点被几个“漏洞”毁了一世英名

简介: 我是 fastjson,是个地地道道的杭州土著,但我始终怀揣着一颗走向全世界的雄心。这不,我在 GitHub 上的简介都换成了英文,国际范十足吧?

网络异常,图片无法展示
|

01、前世今生

我是 fastjson,是个地地道道的杭州土著,但我始终怀揣着一颗走向全世界的雄心。这不,我在 GitHub 上的简介都换成了英文,国际范十足吧?

网络异常,图片无法展示
|

如果你的英语功底没有我家老板 666 的话,我可以简单地翻译下(说人话,不装逼)。

我是阿里巴巴开源的一款 JSON 解析库,可以将 Java 对象序列化成 JSON 字符串,同时也可以将 JSON 字符串反序列化为 Java 对象。

  • 我提供了服务器端和安卓客户端两种解析工具,性能表现还不错。
  • 我提供了便捷的方式来进行 Java 对象和 JSON 之间的互转,toJSONString() 方法用来序列化,parseObject() 方法用来反序列化。
  • 我允许转换预先存在的无法修改的对象(只有 class、没有源代码)。
  • 对 Java 泛型有着广泛的支持。
  • 我支持任意复杂的对象(深度的继承层次)。

2012 年的时候,我就被开源中国评选为最受欢迎的国产开源软件之一。时隔多年,我的流行趋势没有丝毫减退,在 JSON 领域,我敢说我是 NO 1,因为我在 GitHub 上的粉丝数已经超过了 22k,没有任何人敢忽视我这样的成就。

02、使用指南

在使用我的 API 之前,需要先在 pom.xml 文件中引入我的依赖。

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.58</version>
</dependency>

我来写一个简单的测试用例,你看一下。

public class Test {
    public static void main(String[] args) {
        Writer writer = new Writer();
        writer.setAge(18);
        writer.setName("沉默王二");
        String json = JSON.toJSONString(writer);
        System.out.println(json);
    }
}
class Writer {
    private int age;
    private String name;
    // getter/setter
}

Writer 是一个普通的 Java 类,有两个字段,分别是 age 和 name,还有它们俩对应的 getter 和 setter 方法。

main() 方法中创建了一个 Writer 对象,然后调用我提供的一个静态方法 JSON.toJSONString() 来得到 JSON 字符串。

来看一下打印后的结果。

{"age":18,"name":"沉默王二"}

如果想反序列化的话,执行以下的代码即可。

Writer writer1 = JSON.parseObject(json, Writer.class);

调用静态方法 JSON.parseObject(),传递两个参数,一个是 JSON 字符串,一个是对象的类型。

如果想把 JSON 字符串转成集合的话,需要调用另外一个静态方法 JSON.parseArray()。

List<Writer> list = JSON.parseArray("[{\"age\":18,\"name\":\"沉默王二\"},{\"age\":19,\"name\":\"沉默王一\"}]", Writer.class);

如果没有特殊要求的话,我敢这么说,以上 3 个方法就可以覆盖到你绝大多数的业务场景了。

03、使用注解

有时候,你的 JSON 字符串中的 key 可能与 Java 对象中的字段不匹配,比如大小写;有时候,你需要指定一些字段序列化但不反序列化;有时候,你需要日期字段显示成指定的格式。

这些特殊场景,我统统为你考虑到了,只需要在对应的字段上加上 @JSONField 注解就可以了。

先来看一下 @JSONField 注解的定义吧。

public @interface JSONField {
    String name() default "";
    String format() default "";
    boolean serialize() default true;
    boolean deserialize() default true;
}

name 用来指定字段的名称,format 用来指定日期格式,serialize 和 deserialize 用来指定是否序列化和反序列化。

class Writer {
    private int age;
    private String name;
    private Date birthday;
    @JSONField(format = "yyyy年MM月dd日")
    public Date getBirthday() {
        return birthday;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    @JSONField(name = "Age")
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @JSONField(serialize = false,deserialize = true)
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

我建议在 getter 字段上使用 @JSONField 注解。来看一下测试代码。

Writer writer = new Writer();
writer.setAge(18);
writer.setName("沉默王二");
writer.setBirthday(new Date());
String json = JSON.toJSONString(writer);
System.out.println(json);

此时的输出结果如下所示。

{"Age":18,"birthday":"2020年12月17日"}

JSON 字符串中的 Age 首字母为大写,birthday 的格式符合“年月日”的预期,name 字段没有出现在结果中,说明没有被序列化。

04、序列化特性

为了满足更多个性化的需求,我在 SerializerFeature 类中定义了很多特性,你可以在调用 toJSONString() 方法的时候进行指定。

  • PrettyFormat,让 JSON 格式打印得更漂亮一些
  • WriteClassName,输出类名
  • UseSingleQuotes,key 使用单引号
  • WriteNullListAsEmpty,List 为空则输出 []
  • WriteNullStringAsEmpty,String 为空则输出“”

等等等等,更多新技能,等待你去开锁。我这里写个简单的 demo 供你参考。

System.out.println(JSON.toJSONString(writer, 
SerializerFeature.PrettyFormat, 
SerializerFeature.UseSingleQuotes));

对比一下配置前和配置后的结果。

{"Age":18,"birthday":"2020年12月17日"}
{
  'Age':18,
  'birthday':'2020年12月17日'
}

05、我为什么快

众所周知,把 Java 对象序列化成 JSON 字符串,是不可能使用字符串直接拼接的,因为这样性能很差。比字符串拼接更好的办法就是使用 StringBuilder。

StringBuilder 尽管已经很好了,但在性能上还有上升的空间。“自己动手,丰衣足食”,于是我就创造了一个 SerializeWriter 类,专门用来序列化。

SerializeWriter 类中包含了一个 char[] buf,每序列化一次,都要做一次分配,但我使用了 ThreadLocal 来进行优化,这样就能够有效地减少对象的分配和垃圾回收,从而提升性能。

private final static ThreadLocal<char[]> bufLocal         = new ThreadLocal<char[]>();
public SerializeWriter(java.io.Writer writer, int defaultFeatures, SerializerFeature... features){
    this.writer = writer;
    buf = bufLocal.get();
    if (buf != null) {
        bufLocal.set(null);
    } else {
        buf = new char[2048];
    }
}

除此之外,还有很多其他的细节,比如说使用 IdentityHashMap 而不是 HashMap,既可以避免多余的 equals 操作,又可以避免多线程并发情况下的死循环。

/**
 * for concurrent IdentityHashMap
 * 
 * @author wenshao[szujobs@hotmail.com]
 */
@SuppressWarnings("unchecked")
public class IdentityHashMap<K, V> {
    private final Entry<K, V>[] buckets;
    private final int           indexMask;
    public final static int DEFAULT_SIZE = 8192;
}

再比如说,使用 asm 技术来避免反射导致的开销。

网络异常,图片无法展示
|

我承认,快的同时,也带来了一些安全性的问题。尤其是 AutoType 的引入,让黑客有了可乘之机。

1.2.59 发布,增强 AutoType 打开时的安全性

1.2.60 发布,增加了 AutoType 黑名单,修复拒绝服务安全问题

1.2.61 发布,增加 AutoType 安全黑名单

1.2.62 发布,增加 AutoType 黑名单、增强日期反序列化和 JSONPath

1.2.66 发布,Bug 修复安全加固,并且做安全加固,补充了 AutoType 黑名单

1.2.67 发布,Bug 修复安全加固,补充了 AutoType 黑名单

1.2.68 发布,支持 GEOJSON,补充了 AutoType 黑名单。(引入一个 safeMode 的配置,配置 safeMode 后,无论白名单和黑名单,都不支持 autoType。)

1.2.69 发布,修复新发现高危 AutoType 开关绕过安全漏洞,补充了 AutoType 黑名单

1.2.70 发布,提升兼容性,补充了 AutoType 黑名单

在于黑客的反复较量中,我虽然变得越来越稳重成熟了,但与此同时,让我的用户为此也付出了沉重的代价。

网络异常,图片无法展示
|

网络上也出现了很多不和谐的声音,他们声称我是最垃圾的国产开源软件之一,只不过凭借着一些投机取巧赢得了国内开发者的信赖。

但更多的是,对我的不离不弃。

网络异常,图片无法展示
|

最令我感到为之动容的一句话是:

温少几乎凭一己之力撑起了一个被广泛使用 JSON 库,而其他库几乎都是靠一整个团队,就凭这一点,温少作为“初心不改的阿里初代开源人”,当之无愧。

出现漏洞并不可怕,可怕的是发现不了漏洞,或者说无法解决掉漏洞。

为了彻底解决 AutoType 带来的问题,在 1.2.68 版本中,我引入了 safeMode 的安全模式,无论白名单和黑名单,都不支持 AutoType,这样就可以彻底地杜绝攻击。

网络异常,图片无法展示
|

安全模式下,checkAutoType() 方法会直接抛出异常。

06、尾声

不管前面的路还有多少艰难困苦,也不管还要面对多少风言风语,我都会砥砺前行,为了国产开源软件的蓬勃发展,我愿意做一个先驱者,也愿意做一个持久战者。

本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。

相关文章
【Axure教程】中继器表格插入行、复制行和删除行
【Axure教程】中继器表格插入行、复制行和删除行
【Axure教程】中继器表格插入行、复制行和删除行
|
11月前
|
自然语言处理 fastjson Java
记一次细思极恐的FastJson差点引发的大面积故障
作者记录了一次FastJson差点引发的大面积故障的排查过程和解决方案。
|
Ubuntu 关系型数据库 数据库
在Ubuntu 18.04上安装和使用PostgreSQL的方法
在Ubuntu 18.04上安装和使用PostgreSQL的方法
332 1
|
9月前
|
人工智能 自然语言处理 算法
DeepSeek大模型在客服系统中的应用场景解析
在数字化浪潮下,客户服务领域正经历深刻变革,AI技术成为提升服务效能与体验的关键。DeepSeek大模型凭借自然语言处理、语音交互及多模态技术,显著优化客服流程,提升用户满意度。它通过智能问答、多轮对话引导、多模态语音客服和情绪监测等功能,革新服务模式,实现高效应答与精准分析,推动人机协作,为企业和客户创造更大价值。
803 5
|
11月前
|
NoSQL JavaScript 前端开发
Java访问MongoDB
Java访问MongoDB
173 21
|
11月前
|
存储 人工智能 API
七种RAG架构cheat sheet!
RAG 即检索增强生成,是一种结合检索技术和生成模型的人工智能方法。Weaviate厂商给出了七种RAG架构cheat sheet。
909 18
|
11月前
|
人工智能 数据可视化 大数据
为什么甘特图是项目管理中的“必备神器”?
甘特图是项目管理的核心工具,通过时间线直观展示任务的开始、结束时间和依赖关系,帮助团队清晰了解项目全貌,及时发现进度风险并调整资源。其核心功能包括时间线可视化、任务分解与优先级排序、进度追踪及团队协作。结合板栗看板等工具,甘特图能进一步提升项目管理的效率和灵活性,适用于各类团队和企业。未来,甘特图有望与人工智能、大数据等技术结合,实现更智能化的管理。
|
存储 弹性计算 安全
阿里云服务器ECS计算型实例规格族特点、适用场景、指标数据参考
阿里云服务器ECS提供了丰富的计算型实例规格族,专为满足不同场景下的高性能计算需求而设计。包括计算型实例规格族c8y、计算型实例规格族c7、计算型实例规格族c8i等热门计算型实例规格,以及网络增强型的c7nex、密集计算型的ic5等其他计算型实例规格,每一种规格族都经过精心优化,确保在计算性能、存储效率、网络吞吐和安全特性等方面达到最佳平衡。本文将详细解析阿里云服务器ECS中的多个计算型实例规格族,包括它们的核心特点、适用场景、实例规格及具体指标数据,为用户在云计算资源选型时提供全面参考。
阿里云服务器ECS计算型实例规格族特点、适用场景、指标数据参考
|
小程序 搜索推荐 算法
微信小程序外卖管理的设计与实现(论文+源码)_kaic
微信小程序外卖管理的设计与实现(论文+源码)_kaic
|
存储 机器学习/深度学习 人工智能
5个优质免费自然语言处理学习资源 | 语言技术导航
5个优质免费自然语言处理学习资源 | 语言技术导航