HTTP통신 & 통신 프로토콜

이병관·2022년 3월 8일
21
post-thumbnail
post-custom-banner

3주차) HTTP통신 & 통신 프로토콜의 이해

HTTP란

HTTP(Hypertext Transfer Protocol)는 텍스트 기반의 통신 규약으로 인터넷에서 데이터를 주고받을 수 있는 프로토콜입니다.

클라이언트-서버 모델을 따르며, 애플리케이션 레벨의 프로토콜로 TCP/IP위에서 작동합니다.

1. 클라이언트-서버 구조

HTTP는 클라이언트가 HTTP 메시지를 통해 서버에 요청을 보냅니다.

서버가 요청에 대한 결과를 만들어서

응답이 오면 클라이언트가 응답 결과를 열어서 동작을 하게됩니다.

클라이언트와 서버들은 (데이터 스트림과 대조적으로) 개별적인 메시지 교환에 의해 통신합니다.

보통 브라우저인 클라이언트에 의해 전송되는 메시지를 요청(requests)이라고 부르며,

그에 대해 서버에서 응답으로 전송되는 메시지를 응답(responses)이라고 부릅니다.

이 요청과 응답 사이에는 여러 개체들이 있는데,

예를 들면 다양한 작업을 수행하는 게이트웨이, 캐시나 필터링등 여러 역할을 하는 프록시 등이 있습니다.

  • Request Response 구조
  • 클라이언트는 서버에 요청을 보내고 응답을 기다립니다
  • 서버가 요청에 대한 결과를 만들어 응답합니다.

3-tier 아키텍처: 서버에서 데이터베이스를 분리하여 따로 두는 방법을 3-tier 아키텍처라 합니다. 서버는 리소스만 내려주고 실제 리소스들은 데이터베이스에서 가져옵니다.

TCP/IP

TCP/IP는 패킷 통신 방식의 인터넷 프로토콜인 IP (인터넷 프로토콜)와 전송 조절 프로토콜인 TCP (전송 제어 프로토콜)로 이루어져 있습니다.

IP는 패킷 전달 여부를 보증하지 않고, 패킷을 보낸 순서와 받는 순서가 다를 수 있습니다.

TCP는 IP 위에서 동작하는 프로토콜로, 데이터의 전달을 보증하고 보낸 순서대로 받게 해줍니다.

HTTP, FTP, SMTP 등 TCP를 기반으로 한 많은 수의 애플리케이션 프로토콜들이 IP 위에서 동작하기 때문에, 묶어서 TCP/IP로 부르기도 합니다.


2. 비연결성(Connectionless)

비연결성은 클라이언트와 서버가 한 번 연결을 맺은 후, 클라이언트 요청에 대해 서버가 응답을 마치면 맺었던 연결을 끊어 버리는 성질을 말합니다.

서버와 클라이언트의 연결을 유지하는 모델:

클라이언트가 서버에 요청을 보내면

응답을 받고 난 후에도 계속 서버에 접속된 상태로 남아있습니다.

따라서 서버에 접속하는 클라이언트가 많아질수록 연결을 유지하는 서버의 자원이 계속해서 소모합니다.

서버와 클라이언트의 연결을 유지하지 않는 모델:

클라이언트가 서버에 요청을 보내고

응답을 받게되면 바로 클라이언트와 서버의 연결을 끊어버립니다.

따라서 서버는 최소한의 자원만을 사용하게 됩니다.

웹이 진화하면서, 각 웹 사이트에서 필요한 자원 (CSS, 자바스크립트, 이미지 등등)의 양이

해가 갈수록 증가함에 따라 웹 페이지를 다운로드받고 렌더링하기 위해

브라우저는 더 많은 동시성이 필요로 하게 되었습니다.

그때마다 TCP/IP를 위한 3 way Handshake시간이 추가되므로

현재는 여러가지 방법으로 문제를 해결합니다.

해당 내용은 아래에서 좀더 자세히 설명하겠습니다.


TCP 3 way Handshake & 4 way Hand shake

연결 요청시. 3Way HandShake :

  • 클라이언트는 서버와 커넥션을 연결하기 위해 SYN을 보냅니다
  • 서버가 SYN을 받고, 클라이언트로 받았다는 신호인 ACK와 SYN 패킷을 보냅니다
  • 클라이언트는 서버의 응답은 ACK와 SYN패킷을 받고, ACK를 서버로 보냅니다

연결 종료시, 4 Way HandShake:

  • 클라이언트가 연결을 종료하겠다는 FIN플래그를 전송합니다
  • 서버는 일단 확인메시지를 보내고 자신의 통신이 끝날때까지 기다리는데 이 상태가 TIME_WAIT상태입니다
  • 서버가 통신이 끝났으면 연결이 종료 되었다고 클라이언트에게 FIN플래그를 전송합니다.
  • 클라이언트는 확인했다는 메시지를 보냅니다

3. 무상태(Stateless) 프로토콜

서버는 클라이언트 상태를 보존하지 않음(Stateless)

간단하게 서버가 클라이언트의 상태를 보존하지 않는다는것을 뜻합니다.

상태 유지

클라이언트의 요청을 받은 특정 서버만이

해당 유저를 기억하고있기에

항상 그 서버만 응답해야합니다.

만일 이 특정서버가 장애가 난다면,

유저의 상태가 사라지기 때문에

처음부터 다시 서버에 요청해야합니다.

무상태

상태가 유지되지 않기 때문에, 아무 서버에서 호출이 가능하고,

서버에서 장애가 생기더라도 다른 서버에서 응답을 전달 할수 있습니다.

즉 응답 서버를 쉽게 바꿀수 있기 때문에 수평적인 확장에 유리합니다.

HTTP의 역사

0) 초창기. HTTP 0.9

초기에는 버전 번호가 존재하지 않았지만,

이후에 다른 버전들과 구분하기 위해서 0.9라는 버전을 붙이게 되었습니다.

GET /mypage.html

0.9 버전은 단일 라인으로 구성 되었으며 메소드는 GET이 유일했습니다.

또한 상태코드들 역시 존재하지 않았으며 응답 파일 역시 단순하게 파일 내용 자체로만 구성되었습니다

<HTML>
A very Simple HTML page
</HTML>

1) 인터넷 부흥, HTTP 1.0

시간이 지나 1900년대에 인터넷 시장이 점차 커지기 시작하자 W3C가 만들어지며 HTML의 발전이 시작되었고,

이로 인해 HTTP 프로토콜이 개선되기 시작했습니다.

[

RFC 1945 - Hypertext Transfer Protocol -- HTTP/1.0

Hypertext Transfer Protocol -- HTTP/1.0 (RFC )

https://datatracker.ietf.org/doc/html/rfc1945

](https://datatracker.ietf.org/doc/html/rfc1945)

GET /mypage.html HTTP/1.0
User-Agent: NCSA_Mosaic/2.0 (Windows 3.1)

200 OK
Date: Tue, 15 Nov 1994 08:12:31 GMT
Server: CERN/3.0 libwww/2.17
Content-Type: text/html
<HTML>
A page with an image
  <IMG SRC="/myimage.gif">
</HTML>

HTTP 1.0에는 여러가지 특징들이 추가되어 좀더 융통성 있게 확장되었습니다.

  • 버전 정보가 각 요청 사이내로 전송되기 시작했습니다.
  • 상태 코드 라인 또한 응답의 시작 부분에 붙어 전송되어, 브라우저가 요청에 대한 성공과 실패를 알 수 있고 그 결과에 대한 동작을 할 수 있게 되었습니다.
  • HTTP 헤더 개념은 요청과 응답 모두를 위해 도입되어, 메타데이터 전송을 허용하고 프로토콜을 극도로 유연하고 확장 가능하도록 만들어주었습니다.
  • 새로운 HTTP 헤더의 도움으로, HTML 파일들 외에 다른 문서들을 전송하는 기능이 추가되었습니다.

Content-Type : Content-Type은 리소스의 media type을 나타내기 위해 사용됩니다

상태코드

상태 코드는 3자리 숫자로 만들어져 있으며, 첫번째 자리는 1에서 5까지 제공됩니다.

첫번째 자리가 4와 5인 경우는 정상적인 상황이 아니기 때문에 사이트 관리자가 즉시 알아야 하는 정보입니다.

간략하게 상태코드에 대해 설명하자면 다음과 같습니다

  • 1xx(정보) : 요청을 받았으며 프로세스를 계속 진행합니다.
  • 2xx(성공) : 요청을 성공적으로 받았으며 인식했고 수용하였습니다.
  • 3xx(리다이렉션) : 요청 완료를 위해 추가 작업 조치가 필요합니다.
  • 4xx(클라이언트 오류) : 요청의 문법이 잘못되었거나 요청을 처리할 수 없습니다.
  • 5xx(서버 오류) : 서버가 명백히 유효한 요청에 대한 충족을 실패했습니다.

2) 표준 프로토콜 HTTP 1.1

HTTP는 TCP 기반에서 이루어진 프로토콜이기 때문에 연결을 하고, 연결을 끊을때마다 핸드셰이킹이 발생해 다량의 오버헤드가 발생할수 있습니다.

점점 웹의 추세가 텍스트에서 미디어로 변화 하면서 그에 따른 오버헤드가 커지기 시작했습니다.

그리고 사용자의 상태를 유지해야하는 기술들도 요구 되다 보니, 그에 따른 성능개선이 필요해졌습니다.

...현재는 여러가지 방법으로 문제를 해결합니다....

위에서 언급한 해당 부분에 대해 좀더 이야기 해보겠습니다.

TCP의 다양한 제어 기능을 수행하기 위해서 TCP 헤더에는 다양한 정보가 필요합니다. 그러다 보니 데이터를 전송할 때에 꼭 필요하지 않은 처리나 정보가 포함될 수도 있습니다. 이처럼 필요 없는 처리나 정보를 통틀어서 오버헤드(Overhead)라고 부릅니다. TCP의 데이터 전송에는 많은 오버헤드가 발생할 가능성이 있습니다.

따라서 1.1의 가장 큰 특징은 다음과 같습니다.

1. 지속 연결(Persistent Connetion)

언급 했던것 처럼, 초창기에는 컨텐츠의 수가 그렇게 많지 않아 TCP연결이 부담스럽지 않았지만, 컨텐츠의 발달로 인해 많은 양의 컨텐츠를 보여줘야 했습니다. 따라서 TCP의 부담을 줄이기 위해 TCP연결을 재사용 해야하는 필요성이 대두 되었습니다.

왼쪽과 같이 HTTP 1.0 버전에서는 요청마다 TCP를 새로 연결 시켜줘야 하지만, 오른쪽과 같은 HTTP 1.1버전에선

지속 연결(persistent Connetion)을 통해 하나의 TCP 연결을 통해 여러개의 콘텐츠를 요청할 수 있습니다.

덕분에 TCP 세션에 대한 부하를 줄일수 있고, 클라이언트의 응답속도 역시 개선 할 수 있습니다.

웹에서의 커넥션 재사용을 Keep-alive 또는 Connection reuse 라 하며,

HTTP/1.0 에서는 클라이언트가 서버에게 요청하는 Request Header에

다음과 같은 값을 통해 연결을 유지했습니다.

Connection: keep-alive

HTTP/1.1 에서는 이 헤더를 사용하지 않더라도 모든 요청/응답이

Connection을 재사용하도록 설계되어 있으며, 필요없는 경우에만

TCP 연결을 종료하는 방식으로 변경되었습니다.

Connection: close

해제 시에 위와 같은 명시적 Connection 헤더를 이용하며, 그렇지 않은 경우 무조건 연결은 재사용합니다.

1-1 파이프라이닝(PipeLining)

위의 그림에서 연결되었을 때를 보겠습니다

파이프라이닝(PipeLining)은 하나의 연결에서 한번에 순차적인 여러 요청을 연속적으로 하고

그 순서에 맞게 응답을 받는 순으로 서버는 응답하게 됩니다.

HTTP/1.1 에서 클라이언트는 각 요청에 대한 응답을 기다리지 않고,

여러개의 HTTP Request 를 하나의 TCP/IP Packet 으로 연속적으로 Packing 해서 요청을 보냅니다.

파이프라이닝이 적용되면, 하나의 Connection 으로

다수의 Request 와 Response 를 처리할 수 있게끔 Network Latency를 줄일 수 있습니다.

하지만 다르게 생각해보면 응답처리를 미루는 방식이기에, 각 응답에 대한 처리는 순차적으로 진행되어야하고, 그에 따라 후순위의 응답은 지연될 수 밖에 없습니다.

이를 HOL (Head Of Line) Blocking - 특정 응답의 지연이라 합니다

  • 클라이언트에서 요청을 여러 개 동시에 보내면 서버의 응답큐에 쌓인다.
  • 서버에서는 받은 요청을 처리한다.
  • 응답큐에서 데이터 전송의 한계 발생.
  • 선 요청 받은 건을 다 전송하지 못하면, 후전송 건도 늦춰진다.
  • 서버의 버퍼에는 후전송 건이 쌓여 리소스가 소모된다. 

| --- a.png --- |

            | --- b.png --- |

                        | --- c.png --- |

순서대로 첫번째 이미지를 요청하고 응답 받고 다음 이미지를 요청하게 되는데

만약 첫번째 이미지를 요청하고 응답이 지연되면 아래 그림과 같이 두, 세번째 이미지는

당연히 첫번째 이미지의 응답 처리가 완료되기 전까지 대기하게 되며

이와 같은 현상을 HTTP의 HOL Blocking (Head of Line Blocking) 이라 부르며 pipelining의 큰 문제점 중 하나입니다.

| ---------- a.png ------------|                            | -b.png-|

                                         | -c.png- |

RTT( Round Trip Time ) 증가

RTT (Round Trip Time, 왕복 시간)는 패킷망(인터넷) 위에서 패킷을 보내고자 하는 측에서 패킷을 목적지에 보낼 때, 패킷이 목적지에 도달하고 나서 해당 패킷에 대한 응답이 출발지로 다시 돌아오기까지의 시간.

즉, RTT는 패킷 왕복 시간입니다. 네트워크 성능을 측정할 때, RTT는 네트워크 연결의 속도와 안정성을 진단할 때 일반적으로 사용됩니다.

파이프라이닝은 또한 RTT가 증가합니다.

매 요청별로 connection을 만들게 되고

TCP상에서 동작하는 HTTP의 특성상 3-way Handshake 가 반복적으로 일어나고

또한 불필요한 RTT증가와 네트워크 지연을 초래하여 성능이 저하되는 문제점이 발생합니다.

따라서 당시에는 최선의 방법이였으나, 현재에는 산발적으로 존재하는 우려점 때문에

현재 HTTP2.0을 지원하는 요즈음의 브라우저들은 파이프라이닝 기능을 막고있습니다.

2) 도메인 샤딩Domain Sharding(deprecated)

도메인 샤딩 기술은 더이상 사용되지 않는 기능이지만,

이런것이 존재했다 라는 점을 간단히 시사하기 위해 적습니다.

파이프라이닝을 개선하기 위해 브라우저들은 여러개의 Connection을 생성해서 병렬로 요청을 보냅니다.

이를 Domain Sharding이라고 부릅니다.

브라우저별로 Domain당 Connection 개수의 제한이 존재합니다.

3)호스트헤더 (Host Header)

호스트 헤더의 등장으로 동일한 IP주소에 다른 도메인을 호스트하는 기능이 가능해졌습니다.

GET /en-US/docs/Glossary/Simple_header HTTP/1.1
ACCEPT: */*
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 

호스트 헤더가 없던 HTTP1.0 버전에는 위의 요청이 서버로 바로 요청이 될것입니다.

웹서버는 위의 정보로는 어떤 회사의 홈페이지로 접속했는지 알 수 없기 때문에 IP주소로만 구분이 가능합니다.

즉 이전에는 도메인마다 IP를 준비해야했고, 이는 도메인 만큼 서버의 개수도 늘어났습니다.

GET /en-US/docs/Glossary/Simple_header HTTP/1.1
ACCEPT: */*
Host: developer.mozilla.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 

HTTP 1.1의 등장으로 이젠 IP주소 대신 Host의 헤더값으로 고객사를 구별 할수 있게 되어

하나의 웹서버에 여러개의 애플리케이션을 구동할 수 있게 되었습니다.

2 - 1) 시험용 프로토콜 스피디(SPDY)

SPDY는 Google에서 개발한 시험용 프로토콜입니다.

구글은 더 빠른 Web을 실현하기 위해 Latency 관점에서 HTTP를 고속화한

 SPDY(스피디) 라는 새로운 프로토콜을 구현했습니다.

  • HTTP를 통한 전송을 재 정의하는 형태로 구현 

SPDY는 실제로 HTTP/1.1에 비해 상당한 성능 향상과 효율성을 보여줬고 이는 HTTP/2 초안의 참고 규격이 되었습니다.


3) 더 빠르고 효율적으로. HTTP 2.0

HTTP/2의 주요 목표는 다음과 같습니다

  • 전체 요청을 통해 지연 시간을 줄이기
  • 응답 다중화를 지원
  • HTTP 헤더 필드의 효율적 압축을 통해 프로토콜 오버헤드를 최소화
  • 요청 우선순위 지정을 추가하며, 서버 푸시를 지원.

이러한 요구사항을 구현하기 위해 다양한 보조 프로토콜들을 개선하였습니다.

HTTP/2는 HTTP의 애플리케이션 의미 체계를 어떤 식으로도 수정하지 않습니다.

모든 핵심 개념(예: HTTP 메서드, 상태 코드, URI 및 헤더 필드)은 그대로 유지됩니다.

그 대신 HTTP/2는 클라이언트와 서버 간에 데이터 서식(프레임)이 지정되는 방식과 데이터가 전송되는 방식을 수정합니다.

클라이언트와 서버는 전체 프로세스를 관리하며 애플리케이션의 모든 복잡성을

바이너리 프레이밍계층 내에 숨깁니다.

따라서 기존의 모든 애플리케이션을 수정 없이 전달할 수 있습니다.

이 모든 내용을 간단하게 말하자면

모든 노력을 속도향상에 쏟아부었다고 해도 과언이 아닐정도로

속도향상에 많은 노력을 기울인 버전입니다.

주요 특징은 다음과 같습니다.

- 바이너리 프레이밍 계층(Binary Framework)

 HTTP/2 는 TCP 계층과의 사이에 새로운 바이너리 프레이밍 계층 Binary Framework을 통해

네트워크 스택을 구성합니다.

기존에 텍스트 기반으로 Header 와 Data 가 연결되고 있던 1.1 이하 버전과 다르게

HTTP/2 는 전송에 필요한 메시지들을 Binary 단위로 구성하며 필요 정보를

더 작은 프레임으로 쪼개서 관리합니다. 

여기서 데이터를 Binary Encoding 방식으로 관리하는데,

인코딩된 데이터를 다루기 위해서는 반드시 Decoding 과정이 필요하게 됩니다.

즉, HTTP/1.1 버전의 클라이언트는 HTTP/2 버전의 서버와 통신이 불가능합니다.

HTTP2.0에서는 Message 외에 새로운 전송 단위가 추가되었습니다.

  • Frame : HTTP/2에서 통신의 최소 단위, Header 또는 Data
  • Message : HTTP1.1과 똑같은 요청 또는 응답의 최소 단위, 다수의 프레임으로 이루어져 있습니다
  • Stream : 클라이언트와 서버가 맺은 연결을 통해 양방향으로 주고받는 하나 또는 여러개의 메세지들

즉 여러개의 프레임이 하나의 메세지를 이루고, 그 메세지들이 모여 하나의 스트림을 만듭니다.

HTTP/1.x의 요청과 응답의 메세지가 HTTP/2.0에서는 요청과 응답을 스트림으로 묶을수 있는 구조가 된것입니다.

수많은 증기(프레임이)들이 하나의 물방울(메세지)가 되고, 그것들이 모여 양쪽으로 흐르는 물줄기(스트림)가 됩니다.

- 멀티 플렉싱

파이프라인도메인 샤딩으로 해결하기 힘들었던

HOL (Head Of Line) Blocking - 특정 응답의 지연문제를

한 커넥션으로 동시에 여러 개의 메세지를 주고 받을 있으며, 응답은 순서에 상관없이

바이너리 Frame의 스트림 전송으로 처리 할수 있게 되었는데,

이를 멀티플렉싱MultiFlexing이라 합니다

요청, 또는 응답은 뒤섞여 도착하게 되고, 뒤섞인것이 도착한곳. 즉 서버나 클라이언트 단에서

해당 프레임을 다시 재조립합니다.

이처럼 바이너리 프레이밍과 멀티플렉싱을 사용하여 여러 요청과 응답을 병렬처리 할 수 있습니다.

- Stream 우선순위 (Stream Prioritization)

HTTP 메시지가 많은 개별 프레임으로 분할될 수 있고 여러 스트림의 프레임을

멀티플렉싱(Multiplexing)할 수 있게 되면서, 스트림들의 우선순위를 지정할 필요가 생겼습니다.

클라이언트는 우선순위 지정을 위해 ‘우선순위 지정 트리'를 사용하여 서버의 스트림처리 우선순위를 지정할 수 있고,서버는 우선순위가 높은 응답이 클라이언트에 우선적으로 전달될 수 있도록 대역폭을 설정합니다.

- 헤더 압축

클라이언트가 만일 두번의 요청을 보낸다고 가정하면

HTTP/1.x의 경우 두개의 요청 Header에 중복 값이 존재해도 그냥 중복 전송합니다.

그러나 HTTP/2에선 Header에 중복값이 존재하는 경우 Static/Dynamic Header Table 개념을 사용하여

중복 Header를 검출하고 중복된 Header는 index값만 전송하고

중복되지 않은 Header정보의 값은  Huffman Encoding 기법으로 인코딩 처리 하여 전송합니다.

어떤 원리인지 조금 더 자세한 설명입니다

HTTP/2의 Header 압축은 내부적으로 HPACK이라고 불리는 Module이 담당하는데 HPACK은 Huffman Algorithm과 Static Table, Dynamic Table을 통해서 압축을 수행한다. Huffman Algorithm은 자주 나오는 문자열 순서대로 짧은 Bitmap으로 Mapping하여 Data를 압축하는 기법이다. Static Table은 HTTP/2 Spec에 정의된 Table로 HTTP/2 Header로 자주 사용되는 Key-value 값 쌍을 저장하고 있는 Table이다. Dynamic Table은 한번 전송/수신한 Header의 Key-value 값을 임의로 저장하는 Buffer 역할을 수행하는 Table이다. - Static Header Table : 모든 연결에 사용될 가능성이 있는 공용 HTTP 헤더 필드를 제공(ex.올바른 헤더 이름)
- Dynamic Header Table : 처음에는 비어있으며, 특정 연결에서 교환되는 값에 따라 업데이트 된다.

- 서버 푸쉬

HTML을 구성하는데는 많은 컨텐츠가 존재합니다.

서버 푸시 기능이 존재하지않는 HTTP/1.x버전에서 컨텐츠를 요청햇을때, HTML을 요청하고 응답받게 되는데,

그 후 해당 HTML 안에 내장되어있는 자바스크립트파일, CSS파일 등을 또 추가로 요청하는 작업이 필요합니다.

그러나 HTTP/2.0버전에서는 서버 푸쉬를 지원합니다.

이는 요청한 HTML파일 내에 자바스크립트 파일, CSS파일 등이 존재할경우 응답을 전송할때 클라이언트가 요청하지 않은 컨텐츠 까지 같이 보내줄수 있습니다.

이로인해 전송에 필요한 RTT가 줄어들게 되어 응답속도를 개선시킵니다.


4) 차세대 규약, HTTP/3.0

약간 시계를 옛날로 돌려 생각하자면

TCP는 70년대에 만들어진 규약이고, TLS은 보안을 가미시킨 통신 프로토콜입니다.

TCP를 사용하여 수많은 컨텐츠를 교환한다거나, TLS이 일반적으로 사용하는 암호화가 될지 몰랐습니다.

점차 하이퍼텍스트에서 하이퍼 미디어를 넘어가는 현대 사회에서는 더 빠른 통신의 필요성이 대두되기 시작했습니다.

HTTP/2.0의 단점은 다음과 같습니다

TCP Shake와 레이턴시

HTTP의 진화 과정중 항상 빠지지 않고 등장하는것은 바로 연결의 최소화입니다.

HTTP/1.x에선 파이프라이닝, 도메인 샤딩.

HTTP/2.0에선 바이너리 프레이밍 계층과 멀티플렉싱으로 TCP 핸드쉐이킹을 줄였지만

만일 HTTPS연결(HTTP + TLS + TCP)을 한다면 추가적으로 세션의 보안을 위해

SSL의 핸드쉐이킹이 발생합니다.


SSL 핸드 쉐이킹

SSL의 핸드쉐이킹이 어떤 원리인지 조금 더 자세한 설명입니다

Client Hello:

클라이언트가 서버에 연결을 시도하면서 전송하는 패킷입니다.

자신이 사용가능한 Cipher suite 목록, 세션아이디, ssl 프로토콜버전, 랜덤 바이트를 전달합니다.

Cipher suite의 알고리즘에 따라 데이터를 암호화합니다.

Server Hello:

클라이언트가 보낸 ClientHello 패킷을 받아 Cipher suite중 하나를 선택하여 클라이언트에게 알립니다.

또한 서버의 SSL 프로토콜 버전을 보냅니다.즉 클라이언트가 보낸 여러개의 Cipher Suite중 하나를 선택해 전송합니다.

여기서 서버가 선택하는 Cipher Suite는 서버가 사용가능한 가장 높은 등급의 Suite입니다.

Certificate :

서버가 자신의 SSL 인증서를 클라이언트에게 전달합니다. 해당 인증서 내부는 서버가 발행한 공개키가 들어가 있습니다.

이후 클라이언트는 인증기관의 개인키로 암호화된 해당 인증서를 인증기관의 공개키를 사용하여 복호화를 합니다.

복호화에 성공한다면 이는 인증기관이 서명한것이 맞다는 것입니다. 즉 인증서 검증을 합니다.

만일 서버의 공개키가 SSL내부에 존재하지 않는다면 서버의 공개키를 전달하는 Server Key Exchange가 일어납니다. 존재한다면 해당 과정이 생략됩니다.

모든 과정이 끝날경우 서버가 행동을 마쳤음을 알립니다.

Client Key Exchange :

대칭키(데이터를 실제 암호화하는 키, 비밀키)를 클라이언트가 생성하여 서버의 공개키를 사용해 해당 대칭키를 암호화 한 후 서버에 전달합니다.

여기서 전달된 이 대칭키가 바로 SSL Handshake의 목적이자, 데이터를 실제 암호화할 대칭키(비밀키)입니다.

이제부터 이 키를 사용해 클라이언트와 서버간 전송할 데이터를 암호화합니다.

ChangeCipherSpec / Finished

클라이언트와 서버 모두 서로에게 보내는 패킷으로, 교환할 정보들을 모두 교환하였으니 이제 통신할 준비가 되었음을 알리는 패킷입니다.

그리고 Finished 패킷을 보내 SSL HAndshake를 종료합니다.

요악하자면 다음과 같습니다

  • ClientHello: 암호화 알고리즘 나열, 전달
  • ServerHello: 암호화 알고리즘 선택
  • Server Certificate: 인증서 전송
  • Client Key Exchange: 데이터를 암호화할 대칭키 전달
  • Client / ServerHello Done : 암호화 과정에 필요한 데이터 전송 완료
  • Finished: SSL HandShake 종료

HOL (Head Of Line) Blocking

위에서 앞서 설명 한것처럼 파이프라이닝은 keep-alive를 전제로하여

하나의 커넥션에서 한번에 순차적인 여러 요청을 연속적으로 하고

그 순서에 맞춰 응답을 받는 방식으로 지연 시간을 줄이는 방법입니다.

허나 중간에 패킷이 유실되거나, 패킷의 파싱속도가 느리게된다면 후에 들어온 요청들은

앞서 들어온 요청들이 끝날때까지 기다려야만 합니다.

새로운 프로토콜은 어렵다.

이를 해결하기 위한 가장 간단한 방법은 이를 모두 지원하고 보안하는

새로운 프로토콜을 만들면 되지 않는가 하는 생각을 할 수 있습니다.

가능은 하지만 현실적 문제가 존재합니다.

실제 인터넷 트래픽은 거의 TCP와 UDP가 차지하고 있습니다.

가령 HTTP는 TCP 위에서 동작하고 DNS는 UDP나 TCP위에서 동작 합니다.

이러다 보니 TCP나 UDP가 아니면 아예 통과하지 못하는 라우터나 방화벽들도 존재하고 방화벽 설정을 할 때 TCP나 UDP 트래픽이 아니면 모두 막도록 설정하는 경우도 많습니다.

또한 가정용 라우터와 같이 NAT환경을 기본적으로 만드는 경우 TCP나 UDP가 아니라면 제대로 주소 변환이 안 될 수도 있습니다.

어떤 기업이든 나라든 새 프로토콜을 하나 정의했다고 해도

그것이 실제 인터넷상에서 항상 전송 가능한지 보장이 안된다는 것입니다.


4-1)UDP기반의 새로운 프로토콜 QUIC (Quick UDP Internet Connection)

그래서 구글에서는 눈을 돌려 UDP기반의 새로운 프로토콜을 설계했습니다.

이름부터 빠를것같은 퀵(QUIC)프로토콜은 다음과 같은 장점이 있습니다.

정보처리기사의 문제나, 다른 정의집들을 보면 UDP는 TCP에 비해 신뢰도가 없다고 그러는데 왜 UDP를 사용하나요?

정보처리기사를 준비하시거나, 학부에서 공부를 하셧거나 아니면 여러 호기심때문에 검색하셧다면

TCP와 UDP의 차이점은,

TCP는 신뢰 가능한 전송 프로토콜,

UDP는 신뢰성이 없다고 배웠을 것입니다.

신뢰성이 없다는 의미를 더 생각해 보면, UDP는 그냥 데이터를 실어 보낼 수 있을 뿐

그 이외의 기능은 아무것도 정의해 놓지 않았습니다.

UDP는 User Datagram Protocol의 약자인데 U를 Unreliable이라고 생각하는 사람도 적지 않을 것입니다.

TCP 헤더 포맷

UDP 헤더 포맷

전송 순서를 알기 위해서 시퀀스나 ACK 번호를 헤더에 정의해 놓은 TCP와는 달리

UDP 헤더에는 포트 번호와 패킷 크기, 체크섬만 있고, 체크섬은 굳이 채워넣지 않아도 됩니다.

따라서 신뢰성을 보장할 수단이 존재하지 않는 것입니다.

다만 달리 생각하면 이건 백지와도 같은 것입니다.

User Datagram 이라는 의미가 바로 사용자가 정의해서 사용하라는 의미입니다.

즉 특정한 기능을 넣는다면, 원하는대로 데이터를 전송할수 있습니다.

신뢰성 있는 전송을 원한다면 TCP와 같이 최대 전송 패킷수라던지,

서버와 클라이언트 간에 패킷 유실이나 순서 바뀜에 어떻게 대처할 것인지를 상호 정의 한다면

UDP기반의 신뢰성 있는 전송 프로토콜을 만들 수 있습니다.

대표적인 예로 UDT가 있습니다.

UDT: UDP-based Data Transfer Protocol, UDT is built on top of User Datagram Protocol (UDP), adding congestion control and reliability control mechanisms.

1) 레이턴시 감소

기존의 연결. TCP TLS을 사용하여 HTTPS연결을 시도할때는 TLS의 핸드쉐이킹이 각각 발생합니다.

TCP+TLS는 세션키를 주고 받은후, 암호화 연결을 한 뒤 세션키와 데이터를 교환하지만,

QUIC의 경우에는 데이터의 전달과 암호화 과정이 동시에 수행됩니다.

TCP는 연결을 식별하는 연결식별자Connection Identifier로 IP,Port를 사용하지만

Quic은 Connection Id를 사용하여 Initial Key 를 생성하여 데이터와 함께 전송합니다.

그 후, 클라이언트가 디피-헬먼 키 교환값을 반환하여 서버와 클라이언트가 initial key에 동의합니다.

동의한 즉시 데이터 교환이 시작되며 동시에 최종 세션키를 설정합니다.

한번 연결에 성공할 경우 해당 디피-헬먼 키교환값을 저장하고. 다음 연결때 마다 해당 값을 같이 보내

바로 연결을 또 시작하기에 0 RTT로 통신을 할수 있습니다.

2)Connection ID

핸드폰에서 와이파이를 사용하다가, 셀룰러 데이터를 사용해 인터넷에 접속시

일시적으로 약간 느리게 켜지는 경우를 다들 경험해 보셨을겁니다.

이는 클라이언트의 IP가 바뀌기 때문에 새로운 TCP 핸드쉐이킹을 통해 클라이언트를 식별해야하기 때문입니다.

반면 QUIC은 Connection ID를 사용하여 서버와 연결을 생성합니다.

Connection ID는 랜덤한 값일 뿐, 클라이언트의 IP와는 전혀 무관한 데이터이기 때문에

클라이언트의 IP가 변경되더라도 기존의 연결을 계속 유지할 수 있습니다.

이는 새로 연결을 생성할 때 거쳐야하는 핸드쉐이크 과정을 생략할수 있다는 뜻입니다.

3)HOL (Head Of Line) Blocking의 최소화

HTTP/1.1과 HTTP/2의 전송에 있어서 큰 차이점은

멀티플렉싱 기능의 지원입니다. 여러개의 바이너리 프레임으로 병렬 처리하여 받기 때문에,

빠르게 컨텐츠를 받아올수 있었으나 이는 다르게 생각해보면 HOL Blocking에 취약하다는걸 알수있습니다.

멀티플렉싱은 모든 데이터를 하나의 파일중 일부라 생각하기 때문에, 만일 하나의 패킷이 손실난다면

그 뒤의 모든 패킷들이 지연되는 문제가 발생합니다.

허나 QUIC에서는 여러개의 동시 바이트스트림이 존재하고, 스트림별로 손실을 관리하기 때문에

손실이 존재하는 스트림을 제외한 다른 스트림에서의 데이터 전송은 정상적으로 도달할수 있습니다.

구글과 유튜브는 HTTP/3.0

네이버

아직 HTTP/3.0은 그렇게 많은 곳에서 사용되고 있지 않습니다.

몇가지 산발된 문제점이 있어 기업들이 도입하기를 주저하고 있습니다.

TLS 1.3은 여전히 ​​TCP 위에서 독립적으로 실행할 수 있지만 QUIC는 대신 TLS 1.3을 캡슐화합니다.
또한 이전에 암호화 하지않는 헤더 부분을 QUIC은 암호화 하기 때문에 이를 해소해야하는 등 여러가지 걸림돌이 존재합니다

하지만 구글,유튜브는 현재 HTTP/3.0으로 통신하고있습니다

구글

만약 HTTP/3.0의 걸림돌들이 해소된다면

아마 근 시일 안으로 HTTP의 대전제가 바뀔것입니다.

아마도 가까운 미래엔... : HTTP는 TCP/IP UDP기반으로 동작하는 프로토콜입니다


HTTP 메세지

HTTP 메시지는 서버와 클라이언트 간에 데이터가 교환되는 방식입니다. 메시지 타입은 두 가지가 있습니다.

요청(request)은 클라이언트가 서버로 전달해서 서버의 액션이 일어나게끔 하는 메시지고,

응답(response)은 요청에 대한 서버의 답변입니다.

요청과 응답의 구조는 서로 닮았으며 다음과 같은 구조를 가지고있습니다.

  • Start-line: 실행되어야할 요청, 또는 요청 수행에 대한 성공.실패가 기록되있습니다.
  • HTTP 헤더 세트가 옵션으로 들어갑니다. 요청에 대한 설명, 또는 본문에 대한 설명이 들어있습니다.
  • Empty-line: 모든 메타 정보가 전송되었음을 알리는 빈줄입니다.
  • 요청에 관련된 내용, 또는 응답과 관련된 문서가 들어갑니다. 이 존재는 헤더에 명시됩니다.

HTTP 메세지의 시작줄과 HTTP 헤더를 묶어 요청 헤드(head)

HTTP 메세지의 페이로드는 본문(body)라고 합니다

HTTP 요청(Request)

HTTP 요청은 클라이언트가 서버로 보내는 메세지입니다.

시작 줄(Start Line)

Method:

  • HTTP method를 나타냅니다.
  • HTTP method는 수행할 작업(GET, PUT, POST 등)이나 방식(HEAD or OPTIONS)을 알려줍니다.
  • 예를 들어 GET method는 리소스를 받아야 하고, POST method는 데이터를 서버로 전송합니다
  •  Path : 가져오려는 리소스의 경로를 특정 형식에 맞게 작성된 경로입니다.

특정 형식이란 : HTTP method 마다 다른 형식으로 정의됩니다.


⚒ origin 형식

  • ?와 쿼리 문자열이 붙는 절대 경로입니다.
  • POST, GET, HEAD, OPTIONS 등의 method와 함께 사용합니다.
  • POST / HTTP 1.1
  • GET /goingHome.png HTTP/1.0
  • HEAD /destiny.html?query=Kenneth HTTP/1.1
  • OPTIONS /index.html HTTP/1.0

⚒ absolute 형식

⚒ authority 형식

  • 도메인 이름과 포트 번호로 이루어진 URL의 authority component 입니다.
     foo://example.com:8042/over/there?name=ferret#nose
  • HTTP 연결을 구축하는 경우, CONNECT와 함께 사용할 수 있습니다.
  • CONNECT developer.mozilla.org:80 HTTP/1.1

⚒ asterisk 형식

  • OPTIONS 와 함께 별표(*) 하나로 서버 전체를 표현합니다.
  • OPTIONS * HTTP/1.1

  •  Version of the Protocol : HTTP 프로토콜의 버전입니다

Headers 

헤더는 클라이언트와 서버가 요청 또는 응답으로 부가적인 정보를 전송할 수 있도록 해줍니다. 

⚒ Request headers

  • User-Agent, Accept-Type, Accept-Language과 같은 헤더는 요청을 보다 구체화합니다.
  • Referer처럼 컨텍스트를 제공하거나 If-None과 같이 조건에 따라 제약을 추가할 수 있다.

⚒ General headers

  • 메시지 전체에 적용되는 헤더입니다. Connection등을 포함합니다

⚒ Entity headers

  • Content-Length와 같은 헤더는 body에 적용됩니다.
  • body가 비어있는 경우, entity headers는 전송되지 않습니다.

바디(Body)

  • 요청의 본문은 HTTP messages 구조의 마지막에 위치합니다.
  • 허나, 모든 요청에 Body가 필요한 것은 아닙니다. 예를들어 GET, HEAD, DELETE, OPTIONS처럼 서버에 리소스를 요청하는 경우에는 본문이 필요하지 않습니다.
  • POSTPUT과 같은 일부 요청은 데이터를 업데이트하기 위해 사용합니다.

HTTP 응답(Response)

상태줄(Status Code)

응답메세지의 시작줄은 상태줄이라 부르며 다음과 같은 정보와 형식을 가지고 있습니다.

  1. 프로토콜의 버전: 보통 HTTP/1.x 또는 HTTP/2.0입니다
  2. 상태 코드 – 요청의 결과를 나타냅니다. 200, ,404 혹은 302와 같이 상태코드를 돌려줍니다
  3. 상태 텍스트 – 상태코드에 대한 설명을 글로 표현하여, HTTP메세지를 사람들이 쉽게 이해하도록 도와줍니다

상태 줄은 일반적으로  HTTP/1.1 404 Not Found. 같이 생겼습니다.

헤더(header)

  • 응답에 들어가는 HTTP headers는 요청 메세지의 헤더와 동일한 구조를 가지고 있습니다.
  • 대소문자 구분 없는 문자열과 콜론(:), 값을 입력합니다.
  • 값은 헤더에 따라 다릅니다.

⚒ General headers

  • 메시지 전체에 적용됩니다.

⚒ Response headers

  • Vary, Accept-Ranges와 같이 상태 줄을 넘어서는 추가정보를 제공합니다

⚒ Entity headers

  • Content-Length와 같은 헤더는 body에 적용된다. body가 비어있는 경우, Entity headers는 전송되지 않습니다.

바디(Body)

  • 응답의 본문은 HTTP messages 구조의 마지막에 위치합니다.
  • 허나, 모든 요청에 Body가 필요한 것은 아닙니다. 201, 204와 같은 상태 코드를 가지는 응답에는 본문이 필요하지 않습니다.

CORS

CORS는 Cross-Origin Resource Sharing의 줄임말로, 교차 출처 리소스 공유입니다. 즉 다른 출처 접근하여

특정한 자원을 사용할수 있도록 해주는 권한을 부여하고, 그것을 브라우저에 알려주는 체제입니다.

CORS가 왜 필요한가요

CORS가 없이 모든 곳에서 데이터를 요청할 수 있게 되면,

다른 사이트에서 원래 사이트를 흉내낼 수 있게 됩니다.

예를 들자면 기존 사이트와 완전히 동일하게 동작하도록 하여 사용자가 로그인을 하도록 만들고,

로그인 했던 세션을 탈취하여 악의적으로 정보를 추출하거나 다른 사람의 정보를 입력하는 등 공격을 할 수 있습니다. 이렇게 공격을 할 수 없도록 브라우저에서 보호하고, 필요한 경우에만 서버와 협의하여 요청할 수 있도록 하기 위해서 필요합니다.

서로 다른 출처

여기서 서로 다른 출처라는것을 판단하는 기준에 대해 예를 들어 알아보겠습니다

https://www.velog.io:443/fnrkp089?page=1#docker
  • https:// : Protocol
  • www.velog.io : Host
  • 443: port(생략가능)
  • fnrkp089 : path
  • ?page=1 : Query String
  • #docker : fragment

여기서 프로토콜과, 호스트 그리고 포트번호 이 3가지가 같다면 동일한 호스트라 판단하고

아닐경우 다른 호스트로 판단합니다


URL

같은 출처인가

이유

https://www.velog.io/moon




O

프로토콜,호스트,포트번호 같음

https://www.velog.io/moon?q=index

O

프로토콜,호스트,포트번호 같음

https://www.velog.io/moon#sun

O

프로토콜,호스트,포트번호 같음

http://www.velog.io/moon

X

프로토콜 다름

https://www.velog.io:99/moon

X

포트번호 다름

https://www.github.io

X

호스트 다름

CORS는 서버에서 보내는 에러이다?

CORS는 CSRF라고 불리는 브라우저 취약점 공격으로부터 브라우저 사용자를 보호하기 위해만든 기능입니다.

즉, CORS는 브라우저의 구현 스펙에 포함되는 정책이기 때문에,

브라우저를 통하지 않고 서버 간 통신을 할 때는 이 정책이 적용되지 않습니다.

그렇기에 포스트맨에서는 cors를 설정하지 않아도 통신할수 있는 이유입니다.

  • 먼저, 브라우저에게 http요청이 발생한다면 브라우저는 발생한 http요청이 CORS검증을 해야하는 상황인지 판단합니다.
  • 보안 정책상 검증이 필요한 상황에 해당하면 CORS 검증을 서버에 요청합니다.

(사전요청 Preflight Request)

  • 서버에게 응답받은 CORS 검증 요청 결과에 따라서 브라우저는 발생한 http요청을 취소시켜버리고 에러를 발생합니다

CORS의 종류와 동작원리

...HTTP method는 수행할 작업(GET, PUT, POST 등)이나 방식(HEAD or OPTIONS)을 알려줍니다....

Simple Request(단순요청)

단순 요청은, 요청(request)이 아래의 3가지 조건을 만족해야 단순 요청으로 동작합니다.

  • 요청 메서드(method)는 GET, HEAD, POST 중 하나여야 합니다.
  • Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width, Width를 제외한 헤더를 사용하면 안 됩니다.
  • Content-Type 헤더는 application/x-www-form-urlencoded, multipart/form-data, text/plain 중 하나를 사용해야 합니다.

첫 번째 조건은 어렵지 않은 조건이지만 두번째, 세번째 조건은 이야기가 틀려집니다.

두번째 조건은 사용자 인증에 사용되는 Authorization 헤더도 포함되지 않아 까다로운 조건이며,

3번 조건은 많은 REST API들이 Content-Type으로 application/json을 사용하기 때문에 지켜지기 어려운 조건입니다.

따라서 단순요청이 아닌 요청은 사전요청Preflight Request을 통해 접근합니다

Preflight Request(사전 요청)

사전요청(Preflight) 은 일반적으로 웹 애플리케이션을 개발할 때 자주 마주치게 됩니다.

사전요청 방식은, 브라우저는 요청을 한번에 보내지 않고 예비 요청과 본 요청으로 나누어서 서버로 전송합니다.

이때 브라우저가 본 요청을 보내기 전에 보내는 사전 요청을 Preflight라고 부르며,

이 사전 요청에는 HTTP 메소드 중 OPTIONS 메소드가 사용됩니다.

크롬 개발자 도구의 네트워크 탭에 OPTIONS 메서드로 요청이 보내지는 것을 보신 적 있으시다면

CORS를 경험하셨던 것입니다.

Preflight 요청은 실제 리소스를 요청하기 전에 OPTIONS라는 메서드를 통해 실제 요청을 전송할지 판단합니다.

OPTIONS /resources/post-here/ HTTP/2.0 
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7
Access-Control-Request-Headers: content-type,x-pingaruner
Access-Control-Request-Method: POST
Connection: keep-alive
Host: aruner.net
Origin: http://arunranga.com
Referer: http://arunranga.com/
Sec-Fetch-Mode: cors
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36

이렇게 본 요청을 보내기전 OPTION를 통해 예비 요청을 보내게 됩니다.

Preflight의 요청 헤더

  • Origin : Origin 헤더는 cross-site 접근 요청 또는 preflight request의 출처를 나타냅니다.
  • Access-Control-Request-Method: Access-Control-Request-Method 헤더는 실제 요청에서 어떤 HTTP 메서드를 사용할지 서버에게 알려주기 위해, preflight request 할 때에 사용됩니다.
Access-Control-Request-Method: <method>
  • 는 POST, GET, DELETE 등이 포함될 수 있습니다.
  • Access-Control-Request-Headers: 예비 요청을 보낼 때 포함되어, 본 요청에서 어떤 HTTP Header를 사용할 지 서버에게 알려줍니다.

해당 사전 요청들에 대해 서버 역시 응답을 해줍니다.

프로그래머가 Access-Control- 계열의 Response Header만 적절히 정해주면,
OPTIONS 요청으로 오는 예비 요청과 GET, POST, HEAD, PUT, DELETE 등으로 오는 본 요청의 처리는 서버가 알아서 처리합니다.

Preflight의 응답 헤더

HTTP/2.0 200 OK
Access-Control-Allow-Headers: X-PINGARUNER, CONTENT-TYPE
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Origin: http://arunranga.com
Access-Control-Max-Age: 1728000
Cache-Control: max-age=172800
Connection: Upgrade, Keep-Alive
Content-Length: 0
Content-Type: text/plain;charset=UTF-8
Date: Mon, 07 Mar 2022 07:15:06 GMT
Expires: Wed, 09 Mar 2022 07:15:06 GMT
Keep-Alive: timeout=2, max=100
Server: Apache
Upgrade: h2
Vary: User-Agent
  • Access-Control-Allow-Origin: 해당 출처를 허용하겠다는 헤더입니다.
  • Access-Control-Allow-Headers: 실제 요청에서 사용가능한 헤더를 알려줍니다
  • Access-Control-Allow-Methods: 실제 요청에서 사용가능한 메소드를 알려줍니다
  • Access-Control-Max-Age: 예비 요청의 수명을 알려줍니다. 해당 수명 기간동안 예비요청은 캐싱되므로 따로 예비요청을 보내주지 않아도 됩니다.

요청을 두번 보내는 이유는?

조금 생각해보면 예비요청과 본요청을 두번 보낸다는건 통신이 두번 일어난다는 것이고. 이는 서버에 부담이 될수 있습니다.

허나 이 이유는 CORS를 모르는 서버를 위함입니다.

만일 CORS를 모르는 서버가 있다고 가정하고

클라이언트는 브라우저를 통해 특정 메서드로 요청을 보냅니다. 그리고 서버는 CORS를 모르기 때문에 일단 요청을 처리하고 응답하게 되는데 당연히 응답해야 할 Access-Control-Allow-Origin이 없을 것입니다.

브라우저는 서버는 Access-Control-Allow-Origin**이 없기 때문에** cross-origin**에러를 발생**시킵니다.

하지만 이미 특정 요청을 처리하였기 때문에 이는 문제가 됩니다.

따라서 preflight는 이러한 사태를 미연에 방지하고자, 사전요청을 통해 요청의 유효함을 판단하고, 그제서야 서버에 본요청을 날리는 것입니다.


쿠키와 CORS(Credentials)

마지막으로 인증 정보 및 쿠키를 전송하는 지에 대한 여부에 따라,

즉 CORS는 인증 정보를 포함한 요청으로도 나뉩니다.

쿠키는 요청을 할 때에 자동으로 요청에 포함되지만, CORS 요청에서는 아닙니다.

일반적으로 브라우저는 쿠키와 인증 정보에 관해 민감하기 때문에 CORS선 몇가지 설정을 해주어야합니다

Same-Site 속성

일단 쿠키 헤더의 Same-Site 속성에 대해 알아보겠습니다

Same-Site 속성은 다른 도메인 간의 통신에 대한 보안에 대한 설정이고, 3가지 설정값이 있습니다.

  • Strict : 말 그대로 엄격하게 쿠키를 제한합니다. 서로 다른 도메인간의 쿠키 교환을 금지합니다
  • Lax: Strict보다 느슨하게 쿠키를 제한합니다. 서로 다른 도메인간의 쿠키 교환이라 할지라도 top-level navagation 에서 일어나는 안전한 HTTP Method 일 때에는 쿠키를 허용합니다.

Top-level-Navigation : 해당 탭의 URL이 바뀌는 요청에 Lax 쿠키가 포함됩니다. 이를태면 a태그와 link태그를 클릭했을 때 발생하는 GET 요청에 쿠키가 포함되고, 현재 탭의 URL을 바꾸진 않으나 Form의 GET 요청에도 Lax 쿠키가 포함됩니다.

  • None: 서로 다른 도메인간의 쿠키 교환을 제한하지 않습니다. Same-Site가 None인 쿠키는 반드시 Secure가 true여야 합니다.

Secure 속성이 활성화되면 HTTPS 프로토콜 간의 통신에만 쿠키가 포함될 수 있습니다. Same-Site를 None으로 면시 시에는 최소한의 보안 수단으로 Secure 속성을 활성화 해달라는 뜻입니다

다음과 같은 조건이 필요합니다

  • 응답 Access-Control-Allow-Credentials 헤더 활성화
  • 응답 서버에서 허용하는 출처를 "*"가 아닌 직접 명시를 해주어야합니다 (Access-Control-Allow-Origin 헤더에 도메인 명시, 와일드 카드 사용 불가)
  • 쿠키 헤더에 Same-Site: "None" 설정( 다른 Origin간의 통신에도 전송되게 ) ,
  • Secure 설정( Same-Site가 None이면 Secure이 활성화 되어야합니다 )
  • 클라이언트 같은 경우 credentials 옵션을 설정해야합니다.

예를 들자면 Express에선 다음과 같이 작성할수 있습니다.

//SERVER.js
const express = require("express");
const server = express();
const cors = require("cors");

...

server
  .use(cors({ origin: true, credentials: true })) 
  .use(express.json())
  .use(cookieParser());

...
  • cors의 config객체에 origin:true를 작성합니다.
  • Access-Control-Allow-Credentials가 true인 응답은 반드시 Access-Control-Allow-Origin이 명시되어 있어야 하기 때문입니다.
  • origin:true 는 origin 값이 Access-Control-Allow-Origin 로 설정되기 때문에 와일드 카드로 설정되지 않습니다.
......ROUTER

  const requestRefresh = async () => {
    const response = await axios.get(
      "쿠키가 필요한/API/EndPoint",
      {
      	withCredentials: true
      }
    );

    console.log(response);
  };

.....
  • 쿠키를 받을 요청 뿐만 아니라 쿠키를 보낼 요청 또한 withCredentials 옵션을 true로 설정해야합니다.

CORS를 해결하는 여러가지 방법

프록시 서버

클라이언트에서 외부서버로 바로 요청하는것이 아니라 프록시 서버를 사용하여 우회하게 한다면 CORS를 해결할수 있습니다.

클라이언트가 프록시 서버를 통해 다른 네트워크 서비스에 간접적으로 접근시 중간에서 요청을 프록시서버가 가로채서 Access-Control-Allow-Origin 에 자동으로 설정해 줄 수 있습니다

예를들어 직접 구현한 프록시서버가 아닌 다른 프록시서버를 사용할 경우

요청해야할 URL 앞에 프록시 서버 URL을 붙일 수 있습니다

const url_sample = "https://cors-anywhere.herokuapp.com/https://foo.com")

다만 중간에 프록시 서버를 거치기 때문에 속도가 느려질수 있습니다

서버에 Access-Control-Allow-Origin 헤더 세팅하기

서버에 Access-Control-Allow-Origin 헤더를 세팅해 주는것이 기본적인 해결방안입니다.

import * as express from 'express';
const app() = express();

app.get(('/boardList', (req:express.Request, res:express.Response) => {
  res.header('Access-Control-Allow-Origin', '허용하고자 하는 도메인');
  res.send(data)
}) 

미들웨어 사용

Nodejs Express같은 경우에는 CORS 미들웨어를 사용하면 편하게 CORS를 설정할 수 있습니다.

const http = require('http');
const express = require('express');
const app = express();
const server = createServer(app);
const cors = require('cors');

const PORT = 8080;

app.use(cors(corsOptions));

app.get('/', (req, res) => {
	res.send('Hello, World!');
});

server.listen(PORT, () => {
	console.log(`Server running on ${PORT}`);
});
......

///
const corsOptions = {
  origin: function (origin, callback) {
    // db.loadOrigins is an example call to load
    // a list of origins from a backing database
    db.loadOrigins(function (error, origins) {
      callback(error, origins)
    })
  }
}

참고자료

profile
뜨겁고 매콤하고 화끈하게
post-custom-banner

1개의 댓글

comment-user-thumbnail
2022년 10월 14일

글이 너무 길어요!
그래서 오히려 좋아요!

답글 달기