얼마전 회사에서 후배 개발자의 이슈를 함께 고민하던 중 특이점을 발견했습니다.
몇년이 지난 과거 제가 진행했던 프로젝트에 대한 이슈였는데요
GET 방식의 API를 @RequestBody를 이용하여 요청을 받아내고 있었습니다.
애초 제가 해당 프로젝트를 할 때는 그 데이터를 받을 때는 QueryPram으로 받도록 설계했었습니다.
RequestBody로 변경된 이유를 묻자, 취약점 점검 시 해당 부분이 문제점으로 발견되어 고치게 되었다고 하네요...
이슈 처리 바빠서 그 후배에게는 아직 설명을 못해주었지만, GET API를 QueryPram을 RequestBody로 변경한 것은 적절치 못합니다. 오늘은 그 이야기에 대해 포스팅 해보려고 합니다.
과거 프로젝트를 진행할 때 당시 개발자들과의 고민은 "어떻게 해야 MSA 서비스에서 RestFul한 API를 제공할 수 있을지"에 대해서 였습니다. API의 기본 아키텍쳐에 대한 고민이었죠. 그 중 가장 컸던 부분은 필터 기능에 대한 것이었습니다.
간단한 예시를 들자면 게시판에서 여러가지 조건검색을 할 수 있는데 이 필터 조건들을 어떻게 API에게 전달할지에 대한 고민이었죠. 곧장 저희도 RequestBody를 떠올렸습니다. 예를 들면 이러한 JSON구조를 생각해 볼 수 있겠네요.
{
"filterId": "subject", // 어떤 타입의 필터인지? 제목검색, 날짜검색 등등
"filterTarget": "개발", // 어떤 대상으로 필터링할것인지? 내용, 날짜 등등
"filterOrder": "like", // 대상에 대해 어떤 조건으로 필터링할것인지? 포함돼는 문자열, 특정 날짜 범위 등등
}
흠... 확인히 RequestBody로 하면 깔끔하게 보이긴하네요, 쓰고싶어지는 유혹(?)이 듭니다. 하지만 저희는 당시 RequestBody방식을 선택하지 않았습니다. 왜냐하면 이건 RestFul하지 않기 때문이죠. 왜일까요?
기본적으로 RequestBody는 GET에서 사용하길 권장하지 않아요 아래와 같은 이유 때문입니다.
- HTTP 표준 준수: HTTP/1.1 표준(RFC 7231)에 따르면, GET 요청은 요청 본문을 포함하지 않아야 합니다. GET 요청의 목적은 리소스를 조회하는 것이며, 본문을 포함하는 것은 표준을 벗어난 사용입니다.
- 캐싱 문제: GET 요청은 캐시될 수 있어야 합니다. 캐시는 요청 URL을 기준으로 동작하므로, 본문을 포함한 GET 요청은 캐시 시스템에서 올바르게 처리되지 않을 수 있습니다. 본문을 포함한 요청은 캐시 일관성을 유지하기 어렵게 만듭니다.
- 안전성과 멱등성: GET 요청은 안전하고 멱등적이어야 합니다. 즉, GET 요청은 서버 상태를 변경하지 않아야 하고, 동일한 GET 요청을 여러 번 실행해도 결과가 동일해야 합니다. 본문을 포함하는 GET 요청은 이러한 특성을 깨뜨릴 수 있습니다.
- 클라이언트와 서버의 지원 부족: 많은 HTTP 클라이언트 라이브러리 및 서버 프레임워크는 GET 요청의 본문을 지원하지 않거나 무시합니다. 예를 들어, 일부 브라우저는 GET 요청 본문을 보내지 않으며, 일부 서버는 본문을 처리하지 않습니다.
- 표준 툴 및 라이브러리 호환성: 많은 개발 도구와 라이브러리는 GET 요청에 본문이 없다는 가정을 하고 설계되어 있습니다. 따라서, GET 요청에 본문을 포함하면 이러한 도구들과의 호환성 문제를 일으킬 수 있습니다.
또한 첨언을 더 해보자면, GET은 기본적으로 URL에 데이터가 포함되길 기대되며, URL에 요청 파라미터가 포함된 형태 + Body에도 포함된 형태는 일관적이지않으며 API의 복잡도를 오히려 더 늘어나게 된다고 판단했기 때문입니다. 예를들어 다음과 같은 API를 상상해보세요.
URL
[GET] http://localhost:8080/api/board/100
Requst Body
{
"filterId": "subject", // 어떤 타입의 필터인지? 제목검색, 날짜검색 등등
"filterTarget": "개발", // 어떤 대상으로 필터링할것인지? 내용, 날짜 등등
"filterOrder": "like", // 대상에 대해 어떤 조건으로 필터링할것인지? 포함돼는 문자열, 특정 날짜 범위 등등
}
100이라는 boardSeq 게시판에 대해 RequestBody로 검색하는 기능인데... pathParam도 있고, Body에도 조건이 포함되어 있어요. 이러한 구조는 API의 직관성을 해친다고 생각합니다.
그럼 저희는 어떻게 했을까요? 저희는 JSON데이터를 URL인코딩하여 QueryPram으로 처리하였습니다.
URL
[GET] http://localhost:8080/api/board/100?filter=%7B%22filterId%22%3A%22subject%22%2C%22filterTarget%22%3A%22%EA%B0%9C%EB%B0%9C%22%2C%22filterOrder%22%3A%22like%22%7D
Requst Body
없음
URL하나만으로 모든 것을 표현할 수 있습니다. 또한 이러한 특징은 사용자가 URL를 타인에게 공유하거나 응용하여 사용 할 때도 도움을 줄 수 있습니다. :) 훨씬 직관적이죠?
그렇다면 보안취약점에 해당 부분이 문제가 된다면 어떻게 해결해야 할까요? 만약 저였다면 문제로 지적된 파라미터에 대해 암호화 처리 등을 통해 해결하였을 것 같아요. ㅎㅎ 아니면 문제가 될 부분이 아닌데 탐지 되었을 가능성도 있으니 그 부분도 고려를 해서 예외처리를 하던가 해야겠지요? 그 후배가 틀렸다는 이야기를하는 것은 결코 아닙니다 :) 개발에는 좋은 방향성은 존재하지만 정답이 있다고 할 수 없기 때문입니다. 어쨌거나 보안취약점은 해결이 되었으니까요 ㅎㅎ 과거의 깊은 고민과 철학들이 담당자가 달라지며 조금씩 조금씩 사라져가는 것은 아주 조금 슬프긴합니다. ㅋㅋ 사람이 변하고 시간이 흐르면 과거의 고민들도 함께 사라져가는 것 같아요.
여러분들의 생각은 어떤가요? 글 읽어주셔서 감사합니다!
'Develop Issue > Architecture' 카테고리의 다른 글
DASH : Dynamic Adaptive Streaming over HTTP 개념에 대해 (432) | 2017.09.04 |
---|---|
클라우드 서비스의 다양한 형태 : IaaS, PaaS, SaaS (473) | 2016.10.16 |
알고리즘 및 시스템 설계, 데이터베이스 스키마 설계를 시각적으로 표현해주는 웹기반 툴 (476) | 2016.06.26 |
라이브러리와 프레임워크의 차이 (478) | 2016.04.20 |
모듈과 컴포넌트의 차이 (510) | 2016.01.20 |