[GraphQL] 서버를 안전하게 유지하기

본 포스트는 공식 레퍼런스를 참고해 GraphQL을 공부하며 직접 작성한 가이드 입니다.
본 포스트는 2021년 7월 최신 버전인 v16.2를 기준으로 작성되어 있습니다.

prevent recursive gql api call

간단하게 쿼리를 정의함으로써 다양한 요청을 만들 수 있음을 확인했다. 만약에 (개발자의 실수든 뭐든..) 재귀적인 데이터가 생긴다면 어떻게 될까? 테스트를 위해 스키마에 Image를 추가하자.

1
2
3
4
5
6
7
8
9
10
11
type Image {
thumbnail: String!
imageList: [String]
}

type WebToon {
id: ID!
webToonType: WebToonType
title: String
image: Image
}

그리고 간단하게 요청을 보내보자.

1
2
3
4
5
6
7
8
9
10
11
{
getWebToon(id: "3568e088-ec83-11eb-9a03-0242ac130003") {
id
title
webToonType
image {
thumbnail
imageList
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"data": {
"getWebToon": {
"id": "3568e088-ec83-11eb-9a03-0242ac130003",
"title": "여신강림",
"webToonType": "Origianls",
"image": {
"thumbnail": "thumbnail URL",
"imageList": [
"episode imgae1 URL"
]
}
}
}
}

웹툰에 이미지가 잘 들어가서 요청이 온다. 여기서 이제 버그를 만들어보자.

1
2
3
4
5
type Image {
thumbnail: String!
imageList: [String]
image: Image
}

물론 스키마에 맞게 자바 코드 상에서도 버그를 만들어주자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Slf4j
@Component
public class WebToonResolver implements GraphQLQueryResolver {
public WebToon getWebToon(UUID id) {
log.info("getWebtoon request accepted id: {}", id);

Image image = Image.builder().thumbnail("thumbnail URL").imageList(Arrays.asList("episode imgae1 URL")).build();

Image bugImage = Image.builder().thumbnail("bug thumbnail URL").imageList(Arrays.asList("bug episode imgae1 URL")).build();

image.setImage(bugImage);
bugImage.setImage(image);

return WebToon.builder().id(id).title("여신강림").webToonType(WebToonType.Origianls).image(image).build();
}
}

그리고 버그를 호출해보자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
getWebToon(id: "3568e088-ec83-11eb-9a03-0242ac130003") {
id
title
webToonType
image {
thumbnail
imageList
image {
thumbnail
imageList
image {
thumbnail
imageList
image {
thumbnail
imageList
image {
thumbnail
imageList
}
}
}
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{
"data": {
"getWebToon": {
"id": "3568e088-ec83-11eb-9a03-0242ac130003",
"title": "여신강림",
"webToonType": "Origianls",
"image": {
"thumbnail": "thumbnail URL",
"imageList": [
"episode imgae1 URL"
],
"image": {
"thumbnail": "bug thumbnail URL",
"imageList": [
"bug episode imgae1 URL"
],
"image": {
"thumbnail": "thumbnail URL",
"imageList": [
"episode imgae1 URL"
],
"image": {
"thumbnail": "bug thumbnail URL",
"imageList": [
"bug episode imgae1 URL"
],
"image": {
"thumbnail": "thumbnail URL",
"imageList": [
"episode imgae1 URL"
]
}
}
}
}
}
}
}
}

버그가 예술적으로 호출된다. 지금이야 depth가 얼마 되지 않아서 괜찮지만 더 깊어지면 메모리 부족으로 서버가 죽을수도 있다. 새벽 4시에 자다가 일어나서 죽은 서버를 띄우고.. 핫픽스 배포하고.. 끔찍한 일이 일어날 수 있다. 이제 이 끔찍한 일을 방지해보자.

gql 설정에서 max-depth를 설정해줄 수 있다.

1
2
3
graphql:
servlet:
max-query-depth: 4

그리고 같은 요청을 보내보면 요청이 reject된다.

1
2
3
4
5
6
7
8
9
10
11
{
"errors": [
{
"message": "maximum query depth exceeded 7 > 4",
"extensions": {
"classification": "ExecutionAborted"
}
}
],
"data": null
}

물론 depth를 줄이면 요청은 보내진다.

Repository

모든 가이드의 예제 코드는 SongHayoung/springboot-graphql-tutorial에서 확인할 수 있습니다.

Author: Song Hayoung
Link: https://songhayoung.github.io/2021/07/24/GraphQL/graphql-3/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.