【GraphQL】深さ・複雑さ を制限する【Laravel・lighthouse】
GraphQLは、クライアントが欲しい情報を、クライアント側が指定して取得することができます。
その反面、指定され方によっては、サーバー側に大きな負荷がかかる可能性があります。
例えば以下のスキーマがあった場合、
※設計は今回の事象を説明する為のもの なので適当です。
type Query {
fetchAllPlayers: [Player!]! @all
}
type Player {
id: ID!
name: String!
team: Team!
}
type Team {
id: ID!
name: String!
player: [Player!]! @hasMany
}
以下のようにapiを叩くことが可能です。
非常に無駄な叩き方ですし、サーバー側に負荷をかけてしまう可能性もあります。
query{
fetchAllPlayers{
id
name
team {
id
name
player {
id
name
team {
id
name
player {
id
name
team {
id
player {
id
name
team {
id
name
}
}
}
}
}
}
}
}
}
上記の問題を解決するために、
2つのアプローチを行い、解決することを目指します。
depth (深さ)
クエリの深さの制限をかけることにより、高負荷で複雑なネストのクエリを叩くことを防ぐことができます。
config/lighthouse.phpのmax_query_depthを1に設定します。
デフォルトでは0となっており、max_query_depthの設定は無効化されています。
'security' => [
'max_query_complexity' => \GraphQL\Validator\Rules\QueryComplexity::DISABLED,
- 'max_query_depth' => \GraphQL\Validator\Rules\QueryDepth::DISABLED,
+ 'max_query_depth' => 1,
'disable_introspection' => (bool) env('LIGHTHOUSE_SECURITY_DISABLE_INTROSPECTION', false)
? \GraphQL\Validator\Rules\DisableIntrospection::ENABLED
: \GraphQL\Validator\Rules\DisableIntrospection::DISABLED,
],
上記設定で以下クエリを叩くと
query{
fetchAllPlayers{
id
name
team {
id
player {
id
name
}
}
}
}
エラーがレスポンスされ、クエリを叩く際の「深さ」が制限できていることがわかりました。
{
"errors": [
{
"message": "Max query depth should be 1 but got 2.",
"extensions": {
"category": "graphql"
}
}
]
}
complexity(複雑さ)
クエリの複雑さを計算した値が、制限値を超えないか検証を行う方法になります。
lighthouseでの計算方法は
Every field in the query gets a default score 1 (including ObjectType nodes). Total complexity of the query is the sum of all field scores.
とあり、
例えば以下の複雑さのスコアは8ということになります。
query{
fetchAllPlayers{ #+1
id #+1
name #+1
team #+1 {
id #+1
player #+1 {
id #+1
name #+1
}
}
}
}
実際にlighthouse.phpで制限をかけて叩くと、エラーレスポンスを受け取ることができます。
'security' => [
- 'max_query_complexity' => \GraphQL\Validator\Rules\QueryComplexity::DISABLED,
+ 'max_query_complexity' => 1,
'max_query_depth' => \GraphQL\Validator\Rules\QueryDepth::DISABLED,
'disable_introspection' => (bool) env('LIGHTHOUSE_SECURITY_DISABLE_INTROSPECTION', false)
? \GraphQL\Validator\Rules\DisableIntrospection::ENABLED
: \GraphQL\Validator\Rules\DisableIntrospection::DISABLED,
],
{
"errors": [
{
"message": "Max query complexity should be 1 but got 8.",
"extensions": {
"category": "graphql"
}
}
]
}
複雑さ計算のカスタマイズ
またlighthouseでは複雑さ計算のカスタマイズをすることができます。
complexityディレクティブを使います。
fetchAllPlayers: [Player!]!
@all
@complexity(resolver: "App\\GraphQL\\Security\\ComplexityAnalyzer")
childrenComplexityにfetchAllPlayersフィールド配下の計算後の値が入っています。
つまり前回の例だと7 (8-1)。$argsにはクエリ叩く時のvariablesが入っています。
以下では、argsの要素数が0より大きい場合は、childrenComplexityの値を2倍にして、
そうではない場合0を返すカスタムをしました。
<?php
namespace App\GraphQL\Security;
final class ComplexityAnalyzer
{
public function __invoke(int $childrenComplexity, array $args): int
{
return count($args) > 0 ? $childrenComplexity * 2 : 0;
}
}
Discussion