Fastjson2の新しいセキュリティルールを使ったJavaにおける高速JSONパース

5年以上Javaで仕事をしている方なら、Fastjsonという名前には複雑な思いがあるのではないでしょうか。このライブラリは確かにJacksonより高速でした。しかし、AutoTypeによる常套的なデシリアライゼーション脆弱性により、セキュリティ専門家にとって常に頭痛の種となりました。
Alibabaチームはこの問題を誰よりもよく理解していました。2年前、彼らは実質的にライブラリを一から書き直しました。Fastjson2をご紹介します:モダンなJavaバージョンでのセキュリティとパフォーマンスを基盤とした、JSON操作のための再設計されたエンジンです。
セキュリティの変更点
最初のバージョンの主な問題はAutoTypeメカニズムでした。これはパース中にJSONペイロードから任意のJavaオブジェクトを直接作成できました。攻撃者は定期的に組み込みのブロッククラスリストをバイパスし、RCEをトリガーする方法を見つけていました。
Fastjson2では、アプローチが劇的に変わりました。AutoTypeはデフォルトで無効化され、クラススキャンはハードコードされたホワイトリストなしで動作します。SafeModeが導入され、任意のオブジェクトの自動アセンブリを完全に禁止します。
すべての設定フラグはデフォルトでOFFに設定されました。ライブラリは、危害を加える可能性のある型の推測を試みなくなりました。
主な機能
開発者はJava 8からJava 21までの最新のJDKバージョンをターゲットにしました。このエンジンはVector APIの使用、Record型、コンパクト文字列に最適化されています。
ライブラリを触ると、以下の点が際立っています:
- デュアルフォーマットサポート。通常のテキストJSONとバイナリJSONBプロトコルの両方。
- 中間Javaオブジェクトを作成せずにJSONPathで高速な選択的パース。
- 便利なDSLを提供するKotlin向け拡張モジュールと、Spring Framework 5および6のフルサポート。
- GraalVM Native Imageによるネイティブコンパイルが標準で対応。
コードでのクイックスタート
ライブラリを追加するには、pom.xmlに1つの依存関係を追加するだけです:
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.61</version>
</dependency>
パースとシリアライゼーションの簡単な例は、おなじみの形式です:
import com.alibaba.fastjson2.JSON;
// Парсинг строки в объект
User user = JSON.parseObject("{\"name\":\"John\",\"age\":25}", User.class);
// Сериализация объекта в JSON
String json = JSON.toJSONString(user);
Kotlinで書く場合、fastjson2-kotlinモジュールくと簡潔な拡張機能が使えます。toJSONString拡張関数により、コードは驚くほどクリーンになります:
import com.alibaba.fastjson2.*
val text = """{"id":1,"name":"John"}"""
val user = text.to<User>()
val jsonBytes = user.toJSONByteArray()
マイクロサービス向けバイナリJSONBフォーマット
テキストJSONはREST APIの標準のままですが、サービス間通信ではサイズ過多と文字列変換のオーバーヘッドにより優位性を失っています。Fastjson2にはバイナリJSONBフォーマットの組み込み実装が含まれています。
JSONBはデータ型と構造をコンパクトなバイトセットにエンコードします。これにより、通信量の大幅な節約とRPCサービスの高速化が実現されます。
User user = new User(1, "John");
// Сериализация в байтовый массив JSONB
byte[] bytes = JSONB.toBytes(user);
// Сериализация с дополнительным сжатием структурированных данных
byte[] compactBytes = JSONB.toBytes(user, JSONWriter.Feature.BeanToArray);
// Обратный парсинг
User parsedUser = JSONB.parseObject(bytes, User.class);
WriterFeature.WriteNonStringValueAsStringオプションでの圧縮により、オブジェクトがフィールド名を繰り返さない値配列に変換され、フォーマットの効率がProtobufやKryoに近づきます。
JSONPathによる選択的読み取り
多くの場合、マイクロサービスの巨大なJSONレスポンスから取得する必要があるのは1つまたは2つのフィールドだけです。Jacksonのようなクラシックなパーサーは、まずメモリ内に完全なツリーを構築するか、すべてのフィールドを持つオブジェクトを作成するため、CPUとメモリリソースを消費します。
Fastjson2はSQL:2016標準のJSONPath実装でこの問題を解決します。生のバイトストリームから直接データを抽出できます:
byte[] payload = fetchLargeJsonFromNetwork();
// Создаём и кэшируем путь
JSONPath path = JSONPath.of("$.data.user.id");
// Извлекаем значение напрямую из байтового массива
JSONReader reader = JSONReader.of(payload);
Object userId = path.extract(reader);
このテクニックは、高負荷のデータ処理パイプラインで有効です。JVMでの不必要なオブジェクト割当は、すぐにガベージコレクタの一時停止につながるからです。
最初のバージョンからの移行
メジャーバージョンを変更する際に完全な後方互換性を実現することは不可能です。しかし、作者たちは移行を容易にするために努力しました。古いAPIに依存するプロジェクト向けに互換性モジュールが作成されました:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.61</version>
</dependency>
これは古いcom.alibaba.fastjsonとcom.alibaba.jsonパッケージを使用しますが、内部的には新しいv2エンジンで動作します。これにより、レガシーコードで何百ものインポートを書き直さずに更新できます。
ただし、移行時には重要な変更を考慮する必要があります:
- 循環参照のパースはデフォルトで無効化されています。
- Smart Matchフィールドマッチングはデフォルトでは動作しません。
- すべてのシリアライザーオプションはParserConfigを通じて明示的に有効にする必要があります。
Fastjson2は成熟した堅実なプロダクトであることが判明しました。開発者は過去の過ちから学び、危険なデフォルト設定を排除し、Javaエコシステムに真に高速なツールを提供しました。
高負荷サービスを開発している場合、Spring 6 / GraalVM Native Imageで作業している場合、またはKotlinプロジェクトで重量級シリアライゼーションフォーマットの代替を探している場合は、ぜひFastjson2を試してみてください。
関連プロジェクト