REST api에 대한 이해 (put delete 보안 취약점? patch메서드란?)

rest api란?

우선 간단하게 rest api에 대해 요약하면 rest api는 http 프로토콜을 통해 개발자들이 서로 효율적으로 작업하기 위해 정한 규칙이라고 생각하면 된다. 리소스를 중심으로 CRUD에 대한 동작을 GET, POST, PUT, DELETE라는 이름을 통해 명시적으로 api를 사용할 수 있도록 만들었다. rest api 특징은 아래와 같이 여러 가지 존재하지만 굳이 몰라도 큰 문제는 없어 보인다. 더 자세한 설명이 필요하면 아래 유튜브 링크를 통해 정보를 얻어가자.

NAVER D2: 그런 REST API로 괜찮은가(https://www.youtube.com/watch?v=RP_f5dMoHFc)[https://www.youtube.com/watch?v=RP_f5dMoHFc]

rest api 특징

  1. 균일한 인터페이스 모든 API 요청은 동일한 리소스에 대해 동일하게 표시되어야 합니다. 너무나도 당연하 소리. 똑같이 작동해야한다는 뜻으로 동일한 get 요청 보냈는데 다른 값이 반환되면 안됨.(당연히 get 요청으로 조회하려는 데이터 자체가 바뀌면 반환값도 바뀌겠지만, 데이터는 그대로인 상황에서 get 요청을 연속으로 보냈을 때 다른 값이 오면 안된다는 뜻) 또한 메시지 자체로 해당 동작을 이해할 수 있도록 설계되어야하고
  2. 클라이언트-서버 분리 클라이언트와 서버 애플리케이션은 완전히 독립적이어야 합니다. 역할을 확실히 구분할 수 있기 때문.
  3. 무상태 http 프로토콜의 특징과 동일. 상태정보를 유지하지 않음. 그러므로 각 요청에는 처리에 필요한 모든 정보가 포함되어야 함. 예를 들어 로그인한 유저만 이용 가능한 api를 호출한다면 세션 정보도 같이 보내줘야 할 것임.
  4. 캐시 가능성 http 프로토콜을 사용하기 때문에 기존 웹 인프라를 모두 사용할 수 있다. 따라서 캐싱 기능도 사용 가능.
  5. 계층화된 시스템 아키텍처 REST 서버는 다중 계층으로 구성될 수 있으며, 보안로드 밸런싱, 암호화 계층을 추가해 구조상의 유연성을 둘 수있고 PROXY, 게이트웨이 같은 네트워크 기반의 중간매체를 사용할 수있게 합니다.
  6. code-on-demand (optional) 코드를 서버에서 클라이언트로 보내서 실행할 수 있어야한다.(javascript)

※ 참고사항(위 유튜브 링크에서 설명된 내용) 사실 요즘 사람들이 rest api라고 부르며 개발하는 api들은 정확한 정의에 따르면 restful하지 않기 때문에 rest api라고 부르지 못한다고 한다. 그 이뉴는 위 특징 중 1번 균일한 인터페이스 성질을 만족하지 않는 경우가 많기 때문이다. 해당 특징을 만족하기 위해서는 self-descriptive message(메시지 자체로 동작 이해할 수 있어야함)여야 하고, HATEOS(애플리케이션의 전이는 hyperlink를 통애 이뤄져야함)를 만족해야하는데 보통 그렇지 못하기 때문이다.

rest api 요청의 목적지(Host)가 안 써있거나 Content-Type이 json인 경우 해당 key값이 무엇을 의미하는지 명세가 포함되어있지 않으면 self-descriptive하지 않으므로 rest api 조건을 만족하지 못한다.

rest api 요청에 link에 대한 정보가 없으면 HATEOS하지 않으므로 rest api 조건을 만족하지 않는다.

즉, self-descriptive와 HATEOS 두 가지 모두 만족하기 위해서는 일반적으로 많이 사용되는 rest api들을 다음과 같이 수정해줘야 함.

# Self-descriptive 만족시키기
# 1. IANA에 내 json 파일에 대한 명세(미디어 타입)를 넣어둠. 그리고 해당 Media type을 사용한다는 것을 Content-Type을 통해 알려줌.
GET /todos HTTP/1.1
Host: example.org

HTTP/1.1 200 OK
Content-Type: application/vnd.todos+json
{
    "id":1, title="회사 가기",
    "id":2, title="집에 가기",
}


# 2. 내 json 파일에 대한 명세를 특정 url에 넣어두고 해당 url을 Link 헤더에 profile relation을 통해 알려줌.
GET /todos HTTP/1.1
Host: example.org

HTTP/1.1 200 OK
Content-Type: application/json
Link: <https://example.org/docs/todos>; rel="profile"
{
    "id":1, title="회사 가기",
    "id":2, title="집에 가기",
}



# HATEOS 만족시키기
# 1. json 데이터에 link 넣기
GET /todos HTTP/1.1
Host: example.org

HTTP/1.1 200 OK
Content-Type: application/json
Link: <https://example.org/docs/todos>; rel="profile"
{
    {
        "link": "https://example.org/todos/1",
        "title": "회사 가기"
    },
    {
        "link": "https://example.org/todos/2",
        "title": "집에 가기"
    }
}


균일한 인터페이스

  • Self-descriptive
    • 명세 포함

      rest api에 대한 명세를 포함해야 한다. 이때 사용 가능한 방법 중 하나가 바로 Link 헤더에 포함하는 것.

      Link: <https://api.example.com/docs/users>; rel="documentation"
      

      이런식으로 관련 문서 링크를 넣어 이해하기 쉽도록 도와야한다.

    • HATEOS(하이퍼미디어 원칙)

      클라이언트가 서버 자원 구조를 탐색하는데 도움을 줘야함. 이때도 Link 헤더를 이용해 구조를 알려줄 수 있음.

        Link: <https://api.example.com/users?page=2>; rel="next"
      

      이런식으로 다음 페이지 정보를 알려줌으로써 HATEOS를 구현할 수 있다.

캐시 가능성

캐시 가능성을 설정하기 위해서는 http 캐시 헤더를 이용하면 된다. 헤더에 cache-control, Expires, ETag 등의 옵션을 설정해준다.(사실 cache-control만 이용하는 것이 가장 좋다고 생각) 각 캐시 전략에 따른 cache-control 설정법에 대해 더 자세하게 설명한다.

  • 캐시 전략
    • 강제 캐시: 클라이언트 단에 응답이 일정 기간 동안 캐시되도록 설정하여, 이후 동일한 요청이 들어오면 캐시된 응답을 바로 반환합니다.(애초에 서버로 요청 자체를 안해버리는 방식)
        Cache-Control: max-age=3600
      

      위 예시는 캐시가 최대로 살아있을 수 있는 시간을 정해서 해당 시간 동안은 전에 요청 후 응답받은 값을 그대로 이용토록 강제한다.

        Expires: Wed, 15 Aug 2024 07:00:00 GMT
      

      이런식으로 Expires 지시자를 이용해 절대적인 시간을 지정해 응답이 살아있을 수 있는 시간을 정할 수도 있다.

    • 조건부 요청: 클라이언트가 서버에 ETag 또는 Last-Modified 값을 포함하여 요청을 보내면, 서버는 이 값이 최신인지 확인하고 변경이 없으면 304 Not Modified 응답을 반환한다.(서버로 요청은 하지만, 실제 데이터베이스 I/O 등의 작업을 먼저 하는 것이 아니라 메타 데이터 조회를 통해 클라이언트가 요청한 리소스에 대한 변화가 있는지 없는지부터 체크. 만약 변화가 없다면 클라이언트에게 너가 가지고 있는 캐시 그대로 사용해 라고 응답. 그 응답이 바로 304 Not Modified다. 서버에서 메타데이터 조회 등은 데이터베이스 I/O보다 훨씬 간단한 작업이므로 이런 과정을 통해 매번 데이터베이스 I/O 일어나지 않도록 방지하는 것임.)
        If-Modified-Since: Fri, 09 Aug 2024 14:00:00 GMT
      

      위는 클라이언트에서 서버로 요청을 보낼 때 포함하는 헤더로 특정 날짜 이후 리소스가 수정되었는지 확인하는 요청. 변경이 없는 경우 서버에서는 304 Not Modified 상태 코드를 반화하고 클라이언트에서는 캐시해놓았던 값을 그대로 사용한다.

      이외에도 If-Unmodified-Since, If-None-Match, If-Match 등과 ETag(리소스의 고유한 식별자)를 이용해 캐시를 사용할지 새로운 값을 조회할지 결정한다.

    • 캐시 무효화: 리소스가 변경되거나 만료되었을 때 캐시를 무효화하거나 업데이트합니다. 예를 들어, 새로운 데이터가 들어오면 서버에서 캐시를 강제로 갱신할 수 있습니다.

하지만 실제로 위와 같이 실제 정의대로 완벽히 설계된 rest api는 거의 본 적이 없다. 위 정의를 완벽히 따라하지 않아도 문제가 생길 것 같지는 않지만 일단 알아두면 좋을 것 같다.

왜 get, post만 쓸까?

나는 기존 레거시 코드를 확인하다가 get, post 메서드로만 구성된 것을 목격했다. 이때 인터넷을 뒤져보았고 금융기관 혹은 공공기관의 코드에서도 마찬가지로 get과 post 메서드만 사용한다는 글들을 확인할 수 있었다.

  • 참고자료 (https://blog.naver.com/choo_co/221276997559)[https://blog.naver.com/choo_co/221276997559] (https://www.dogdrip.net/298998326)[https://www.dogdrip.net/298998326]

다양한 글을 통해 확인한 결과 put과 delete 메서드에 보안상 문제가 있다는 이유로 해당 메서드를 사용하지 않는 경우가 있었다. 하지만 대부분 명확한 근거는 불분명. 대부분 카더라 정보일 뿐이며 이런 방식은 단순히 관습적인 것으로 보인다.

때문에 굳이 get, post만 이용할 이유는 없어보인다. 공식적인 정보에 따라 get, post, put, delete 모든 메서드를 이용해 개발하는 방식이 더 합당하다고 생각된다.

그럼 보안을 더 강화하려면 어떻게 할까?

사실 rest api 메서드 종류에 따라 보안에 큰 차이는 없다. 그나마 있다면 get 메서드의 경우 쿼리 스트링으로 데이터를 전송할 때 url에 정보들이 노출되므로 예민한 정보를 담으면 안된다 정도. 하지만 이는 실질적으로 큰 의미가 없어보인다. 만약 해커가 네트워크 패킷을 탈취했다면 post 요청도 결국 body를 까보면 안에 정보들을 모두 확인할 수 있기 때문에 get 요청은 데이터를 쿼리 스트링으로 url에 담고 post 요청은 데이터를 본문에 숨겨서 넣으니 보안에 차이가 난다라고 볼 수 없다.

결국 rest api 메서드간 보안 차이는 거의 없다. 보안을 높이기 위해서는 httsp 프로토콜을 이용해 평문(암호화되지않은 일반 텍스트)을 암호화해 전송하거나 토큰값을 이용한 인증 절차를 추가하는 것이 좋다고 생각한다.

rest api 메서드 종류

나는 여태까지 rest api는 단순히 4가지 존재한다고 알고 있었다. get, post, put, delete. 하지만 아니었다. patch, head, option 등 더 많은 종류의 메서드가 존재했다.

  1. GET: 역할: 서버로부터 데이터를 가져옵니다. 예시: 블로그 글 목록을 조회하는 경우 GET /posts를 사용합니다.
  2. POST: 역할: 서버에 새로운 데이터를 추가하거나 작성합니다. 예시: 새로운 댓글을 작성하는 경우 POST /comments를 사용합니다.
  3. PUT: 역할: 서버의 데이터를 갱신하거나 수정합니다. 이때 갱신되거나 수정되는 값은 데이터 전체에 해당됨. 또한 원하는 위치에 대한 데이터가 있으면 수정. 없으면 추가. 예시: 사용자 정보를 모두 업데이트하는 경우 PUT /users/1를 사용합니다. 이때 보통 path variable로 들어간 1이라는 값은 users 테이블의 기본 키(pk)를 의미합니다. 즉, 1번 pk 값의 데이터를 수정하겠다는 뜻. 이때 1번 pk에 해당하는 값이 없다면 pk값이 1번인 새로운 데이터를 추가합니다. 그리고 해당 메서드를 통해 수정을 하려고 하면 수정하고 싶은 내용을 본문에 넣어서 보내게 되는데, 이때 전체 데이터를 모두 보내야한다. 만약 내가 바꾸고 싶은 데이터의 일부 정보만 본문에 넣어서 보내면 해당 부분은 수정이 되지만 나머지 데이터는 빈 값으로 대체된다. 즉, 유지하고 싶은 부분의 데이터도 그대로 작성해서 본문에 넣어줘야함.
  4. PATCH: 역할: 리소스의 일부분을 수정합니다. 이때 단순 수정할 값을 넘겨주는 방식이 아니라 기존 값에 +10을 해라 이런식으로도 이용이 가능합니다. 예시: 사용자 정보를 일부 업데이트하는 경우 PATCH /users/1를 사용합니다. 요청 본문에 내가 수정하고 싶은 일부의 정보만 넣어도 됩니다. 그럼 해당 부분만 변경됩니다.
  5. DELETE: 역할: 서버의 데이터를 삭제합니다. 예시: 게시물을 삭제하는 경우 DELETE /posts/42를 사용합니다.
  6. HEAD: 역할: 서버 리소스의 헤더 (메타 데이터)를 취득합니다.
  7. OPTIONS: 역할: 리소스가 지원하는 메서드의 목록을 취득합니다.

참고 자료 : (https://ko.wikipedia.org/wiki/HTTP)[https://ko.wikipedia.org/wiki/HTTP]

참고 자료에 나온 컬럼 설명

  • 안전 위 자료를 통해 get 메서드는 안전하지만, 다른 post, put, delete, patch 메서드는 안전하지 않다고 한다. 이 이유는 get은 단순 조회이므로 서버에 문제를 발생시킬 여지가 적지만 다른 메서드들은 서버 상태를 변경하거나 리소스를 추가하거나 수정 혹은 삭제하므로 주의가 필요하다는 뜻이다.

  • 멱등 멱등이란 연속한 요청을 보냈을 때 동일한 결과값을 얻을 수 있는지다. 이때 결과값이란 요청에 의한 응답을 의미하는 것이 아니다. 리소스의 상태를 의미한다. 즉, 1번 실행했을 때와 2번 실행했을 때 동일한 상태를 만들 수 있냐를 묻는 것이다.

    • get : o
      get 요청 1번 보냈을 때 : 데이터베이스 데이터 중 a라는 값 조회 get 요청 2번 보냈을 때 : 데이터베이스 데이터 중 a라는 값 조회 (똑같은 조회니까 달라지는 거 없음)
    • post : x post 요청 1번 보냈을 때 : 데이터베이스 데이터 중 a라는 값 추가 post 요청 2번 보냈을 때 : 데이터베이스 데이터 중 a라는 값 또 추가 (1번 요청했을 때보다 a라는 값이 1개 더 많아진다)
    • put : o put 요청 1번 보냈을 때 : 데이터베이스 데이터 중 a라는 값 b로 수정 put 요청 2번 보냈을 때 : 데이터베이스 데이터 중 b라는 값 b로 수정 (위에서 이미 b로 수정되었긴 하지만 그래도 또 b로 수정. 하지만 결과는 동일하다)
    • patch : x patch 요청 1번 보냈을 때 : 데이터베이스 데이터 중 a라는 값 b로 수정 patch 요청 2번 보냈을 때 : 데이터베이스 데이터 중 b라는 값 b로 수정 (이런 경우에는 멱등함)

      patch 요청 1번 보냈을 때 : 데이터베이스 데이터 중 a라는 값 +10. –> a=10 patch 요청 2번 보냈을 때 : 데이터베이스 데이터 중 a라는 값 +10. –> a=20 (이런 경우에는 멱등하지 않음)

    • delete : o delete 요청 1번 보냈을 때 : 데이터베이스 데이터 중 a라는 값 삭제 delete 요청 2번 보냈을 때 : 데이터베이스 데이터 중 a라는 값은 없으므로 삭제하지 못함 (하지만 결과는 동일하게 a라는 값이 삭제된 상태. 이때 delete는 없는 값을 삭제하려 했기 때문에 오류 코드를 반환한다. 가끔 여기서 위에서는 완벽하게 a를 삭제했다는 메시지를 반환받고 아래에서는 오류 코드를 반환하니 반환하는 값이 달라 멱등하지 않지 않은 것 아니냐는 식으로 이해하는 경우가 많다. 하지만 위에서 말했듯 반환값이 같은지를 따져서 멱등한지를 확인하는 것이 아닌 서버의 상태나 리소스의 상태가 동일한지 따져서 멱등한지를 구분하기 때문에 이 경우는 동일하게 a 데이터가 삭제된 상태이므로 멱등하다고 하는 것)

results matching ""

    No results matching ""