현재 네트워크는 패킷교환 방식으로 구축되어 있다. 패킷이라는 단위로 데이터를 잘게 나누어서 전송하는 것이다. 각 컴퓨터는 자신의 IP 주소를 가지고 있다. 패킷에는 전송할 데이터의 조각뿐만 아니라 출발, 목적지 IP 주소 등의 정보를 가진다. 네트워크를 이루고 있는 각 노드들은 패킷을 받으면 목적지 IP 주소로 해당 패킷이 도달할 수 있도록 전송하게 된다. 네트워크로 서버에 어떤 데이터를 요청하는 과정과 서버에서 응답을 돌려주는 과정 모두 이런 단계를 거친다. 물론 패킷교환 방식도 단점이 있다. 첫 번째는 비연결성이다. 비연결성이란 패킷을 받을 대상이 없거나 현재 서비스가 불가능한 상태여도 일단 패킷을 전송한다는 의미이다. 패킷을 보내는 입장에서는 패킷을 받을 곳의 상태를 알 방법이 없기 때문이다. 두 번째는 비신뢰성이다. 비신뢰성이란 중간에 패킷이 사라질 수도 있으며 패킷의 전송 순서를 보장할 수도 없다는 의미이다. 패킷을 받는 입장에서 모든 패킷이 도착했는지 알 방법이 없기 때문에 데이터가 손실될 수도 있으며 패킷들이 서로 다른 노드 경로로 전달될 수 있기 때문에 의도하지 않은 순서대로 패킷이 도착할 수 있는 것이다.
이런 단점들을 보완하기 위해서 네트워크 계층 구조의 개념이 등장했다.
네트워크 계층 구조는 위의 이미지와 같이 네트워크 프로토콜을 OSI 7계층 혹은 TCP/IP 4계층으로 구성하는 개념이다. 사실은 TCP/IP 4계층은 OSI 7계층보다 먼저 등장한 개념이긴 하지만 OSI 7계층이 더 공식적인 계층화 모델로써 제시되기도 했으며 요즘은 오히려 OSI 7계층을 기반으로 TCP/IP 4계층을 설명하는 케이스가 더 많다고 한다. 조금 더 자세한 내용은 따로 설명하기로 하고 우선 IP 패킷의 단점을 보완할 수 있는 방법부터 살펴보자.
실제로 어떤 요청을 하게 되면 우선 HTTP 메세지가 생성될 것이다. 이것이 네트워크 환경에 연결할 수 있게 만들어진 연결부인 네트워크 소켓을 통해 전달되며 TCP(Transmission Control Protocol) 세그먼트를 생성한다. TCP 세그먼트에는 패킷의 출발지와 목적지의 IP 정보를 보완하기 위핸 포트 정보나, 전송 제어 및 순서나 검증 정보도 담겨 있다.
이 TCP의 연결 방식을 흔히 3 way handshake라고 부른다. 클라이언트에서 데이터를 보내기 전에 접속을 요청하는 SYN 패킷을 먼저 보내고 서버에서는 이 패킷을 전송받았을 때 이에 대한 응답을 받았을 때 비로소 본 데이터를 보내는 3단계로 이루어져 있으며 또한 이 과정에서 IP 패킷만을 사용했을때의 비연결성을 해결할 수 있다. 또한 본 데이터를 보냈을 때 TCP 세그먼트 안에 패킷의 순서와 관련된 정보가 담겨 있기 때문에 순서가 틀렸다면 다시 데이터를 요청하기 때문에 비신뢰성 또한 해결된다. 참고로 UDP(User Datagram Protocol)라는 프로토콜도 존재하는데 이것은 포트와 체크섬 필드 정보만 추가된 훨씬 단순화된 프로토콜로 3 way handshake방식을 사용하지 않기 때문에 신뢰성은 TCP에 비해 떨어지지만 오버헤드가 훨씬 적어서 스트리밍이나 게임과 같이 빠른 실시간 통신이 필요한 경우는 오히려 이런 프로토콜을 사용하는 경우가 많다고 한다.
지금부터 네트워크 계층 모델에 대해서 조금 더 자세하게 알아보자. 이런 계층 모델은 ISO(International Organization for Standardization)라고 하는 국제표준화기구에서 1984년에 제정한 표준 규격으로 네트워크 유형에 관계없이 상호 통신이 가능하게 만들기 위한 규격이 필요해서 등장하게 되었다. OSI 7계층 모델은 네트워크를 이루고 있는 구성요소들을 7단계로 나누고 각 계층의 표준을 정해놓은 모델이다. 이런 표준화 덕분에 호환성 문제를 해결했을 뿐만 아니라 문제가 발생했을 때 그 원인을 찾고 설명하는 것도 훨씬 쉬워졌다. 각 계층은 다음과 같이 구분할 수 있다.
1계층 - 물리 계층: 시스템 간의 물리적인 연결과 전기 신호를 변환 및 제어
ex) 디지털 또는 아날로그로 신호 변경
2계층 - 데이터링크 계층: 네트워크 기기 간의 데이터 전송 및 물리주소(ex MAC 주소)를 결정
ex) 브리지 및 스위치, MAC 주소
3계층 - 네트워크 계층: 실제 네트워크 간에 데이터 라우팅
ex) IP 패킷 전송
4계층 - 전송 계층: 데이터들이 실제로 정상적으로 보내지는지 확인
ex) TCP/UDP 연결
5계층 - 세션 계층: 세션 연결의 설정과 해제, 세션 메시지 전송 등의 기능
6계층 - 표현 계층: 응용 계층으로 전달하거나 전달받는 데이터를 인코딩 또는 디코딩
ex) 문자 코드, 압축, 암호화 등의 데이터 변환
7계층 - 응용 계층: 사용자가 실행하는 응용 프로그램(ex Google Chrome)
ex) 이메일 및 파일 전송, 웹 사이트 조회
즉, 물건을 하나 보내는 과정이라고 간단하게 바꿔서 생각해보면 물리 계층은 실제로 물건을 옮기는 과정, 데이터링크 계층은 물건을 옮길 수단에 물건을 싣는 과정, 네트워크 계층은 물건의 배송될 경로를 정하는 과정, 전송 계층은 물건이 제대로 보내졌는지를 확인할 수 있는 배송 시스템, 세션 계층은 받는 사람과 보내는 사람의 주소를 적는 것, 표현 계층은 물건을 포장하는 과정, 응용 계층은 보내고 싶은 물건을 준비하는 과정이라고 생각할 수 있을 것 같다.
데이터는 이처럼 OSI 7계층 모델은 송신 측의 7계층과 수신 측의 7계층을 거쳐서 전달되며 각 계층은 독립적이라서 다른 계층의 영향을 받지 않는다. 데이터를 전송하는 측에서는 응용 계층에서 시작해서 하위 계층으로 데이터를 전달하는데 이 때 각 계층에서 필요한 정보를 헤더에 각각 추가해나가며 이런 것을 캡슐화라고 부른다. 물리 계층에 도달한 데이터는 전기 신호로 변환되어 수신 측에 전송되며 이 전기 신호는 수신 측에서 다시 7계층을 타고 올라가면 데이터로 변환된다. 상위 계층으로 전달하면서는 각 계층에서는 수신 측의 같은 단계에서 붙였던 헤더들을 인식하고 제거해나가게 되는데 이것은 역캡슐화라고 부른다. 역캡슐화를 모두 거친 마지막 수신 측의 응용 계층에 도달하면 처음 전송하려고 했던 원본 데이터만이 남게 된다.
TCP/IP 4계층 모델은 OSI 7계층 모델에 비해 조금 더 실무적으로 이용할 수 있도록 현실에 맞춰 단순화된 모델이다. 우선 각 계층에 대한 설명부터 알아보자.
4계층 - 어플리케이션 계층: OSI 7계층의 세션 계층 + 표현 계층 + 응용 계층. TCP/UDP 기반의 응용 프로그램을 구현
ex) FTP, HTTP, SSH
3계층 - 전송 계층: OSI 7계층 전송 계층. 통신 노드 간의 연결을 제어하고, 신뢰성 있는 데이터 전송
ex) TCP/UDP
2계층 - 인터넷 계층: OSI 7계층의 네트워크 계층. 통신 노드 간의 IP 패킷을 전송하는 기능 및 라우팅
ex) IP, ARP, RARP
1계층 - 네트워크 인터페이스 계층: OSI 7계층의 물리 계층 + 데이터 링크 계층.
ex) LAN, 패킷
TCP/IP 4계층 모델의 실용성 덕분에 현대의 인터넷 표준은 TCP/IP 4계층에 가깝다. 또 오해하기 쉬운 것은 서비스를 요청하는 클라이언트와 제공하는 서버 모두 응용 계층 혹은 어플리케이션 계층에서 동작하는 것이며 그 사이의 데이터를 전송하는 과정이 그 밑의 하위 계층이라고 볼 수 있다.
웹 페이지를 개발하는 입장에서 그 외의 단계의 여러 표준들을 전부 알고 있을 필요는 없지만 그 중에서 웹 개발자들도 반드시 숙지하고 있어야 할 프로토콜이 있는데 바로 응용 계층의 대표적인 프로토콜인 HTTP이다. HTTP 메세지에 대한 대용들은 이전에 네트워크의 기초를 다루면서 설명했었다. 이번 포스트에서는 HTTP에 대해 조금 더 자세하게 알아보려고 한다.
우선 HTTP에도 버전이라는게 존재한다. HTTP 요청 메세지나 응답 메세지 모두 첫 줄에 사용하고 있는 HTTP 프토코콜 버전이 있는데 주로 사용하고 있는 것은 HTTP 1.1 버전이며 2015년에 이미 HTTP/2 버전이 공개되었고 점차 전환이 진행중이라고 한다. 여기까지는 TCP 기반이며 현재 개발중이라고 알려진 HTTP/3의 경우는 UDP를 사용하여 로딩과 데이터 전송 속도를 향상시킬 예정이다.
HTTP의 특징이라고 한다면 클라이언트-서버 구조를 먼저 들 수 있다. 말 그대로 클라이언트에서 요청을 보내고 서버에서는 그에 대한 응답을 만들어 보낸다는 의미이다. 그 다음으로는 무상태성이 있다. 무상태성에 대해서는 이전 포스트에서 간단하게 언급했었는데 이 또한 말 그대로 서버에서는 클라이언트의 상태를 보존하지 않는다는 의미이다. 그래서 클라이언트가 이전에 요청했던 사항을 유지하지 않기 때문에 클라이언트에서 모든 데이터를 한번에 전송해야한다. 이것이 단점이라고 생각할 수 있지만 오히려 상태를 유지하는 경우가 상황이 더 복잡해진다. 상태를 유지하는 경우에는 처음 요청을 받은 서버에서 모든 요청을 전부 처리하거나 혹은 이전 요청의 정보를 다른 서버에게 전송해야하는 과정이 필요하다. 한편 무상태성 프로토콜은 어차피 클라이언트에서 모든 정보를 한번에 전송하기 때문에 서버를 추가적으로 투입해도 아무런 문제가 없다. 물론 모든 요청을 전부 무상태로 처리하는 것에는 한계가 있기는 하다. 예를 들어 로그인 정보와 같은 경우는 유저의 상태를 유지해야하기 때문에 브라우저의 쿠키나, 서버 세션, 토큰 등의 기법을 활용할 수 있다. 마지막 특징은 비연결성이다. 이것은 HTTP에서는 실제 요청을 주고 받는 단계가 끝나면 TCP/IP 연결을 끊는다는 의미이다. 이는 연결을 계속해서 유지하는 모델에 비해 서버의 자원 소모를 막을 수 있으며 특히 트래픽이 많지 않고 빠른 응답이 가능한 경우에는 매우 효율적이다. 단, 연결을 끊기 때문에 새로 요청을 하기 위해선 TCP/IP 연결을 새로 맺어야 하고 그 때마다 앞서 설명한 3 way handshake 다시 거쳐야 하기 때문에 오버헤드가 크다. 그래서 HTTP/1.1 버전에는 지속 연결이 도입되서 각각의 자원들을 요청하고 모든 자원에 대한 응답이 돌아온 후에 연결을 종료하기 때문에 속도를 단축할 수 있게 되었다.
HTTP 메세지에 대해서도 더 자세하게 알아보자.
HTTP 메세지는 헤더와 바디로 구분할 수 있다고 했었다. 바디에는 페이로드를 통해 표현 데이터를 전달하는 기능 뿐이라서 특별히 더 설명할 부분은 없다. 반면 헤더에는 "이름 : 값"의 형태로 다양한 정보들이 있는 것을 볼 수 있는데 이것은 HTTP 전송에 필요한 부가정보들을 담고 있다. 예를 들면 바디에 들어가 있는 표현 데이터의 형식이라던가 압축 방식 등의 정보를 담고 있다. 어떤 의미인지 알아두면 좋은 헤더들은 다음과 같다.
- Content-Type: 표현 데이터의 양식
- 미디어 타입이나 문자 인코딩 등의 정보
- ex) Text/html;chatset=utf-8, application/json
- Content-Encoding: 표현 데이터의 압축 방식
- 데이터를 전달하는 곳에서 압축한 인코딩 정보, 읽는 쪽에서 이 정보를 바탕으로 압축 해제
- ex) gzip, deflate
- Content-Language: 표현 데이터의 자연 언어
- 표현 데이터에서 사용하고 있는 언어
- ex) ko, en
- Content-Length: 표현 데이터의 길이
- 바이트 단위의 표현 데이터 길이, Transfer-Encoding을 사용할 땐 이 헤더를 사용하지 않음
지금부터는 요청에서만 사용되는 헤더이다
- From: 유저 에이전트의 이메일 정보
- 일반적으로 잘 사용되진 않지만 검색 엔진에서 종종 사용
- Referer: 이전 웹 페이지 주소
- 현재 요청된 페이지의 이전 웹 페이지 주소로 예를 들어 A → B로 이동하는 경우 Referer: A를 포함해서 요청
- 유입 경로를 수집할 수 있음
- User-Agent: 유저 에이전트 어플리케이션 정보
- 클라이언트의 웹 브라우저 정보 등의 통계 정보
- 어떤 종류의 브라우저에서 장애가 발생하는지 파악 가능
- ex) user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)
- Host: 요청한 호스트 정보
- 필수 헤더
- 하나의 서버가 여러 도메인을 처리해야 할 때 혹은 하나의 IP 주소에 여러 도메인이 적용되어 있을 때 어떤 도메인으로 요청이 왔는지 파악하는 용도
- Origin: 요청을 시작한 주소
- 서버로 POST 요청을 보낼 때 사용, 이 주소와 받는 주소가 다른 상황이 바로 CORS 에러
- Authorization: 인증 토큰을 서버로 보낼 때 사용
- 토큰의 종류(ex. Basic) + 실제 토큰 문자의 구조
- ex) Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l
지금부터는 응답에서만 사용되는 헤더이다
- Server: 요청을 처리하는 ORIGIN 서버의 소프트웨어 정보
- ex) Server: Apache/2.2.22 (Debian)
- Date: 메시지가 발생한 날짜와 시간
- ex) Date: Tue, 15 Nov 1994 08:12:31 GMT
- Location: 페이지 리디렉션
- 브라우저에서 3xx번대의 상태 코드를 받은 경우 이 헤더에 있는 위치로 리다이렉트 시킴
- 201(Created)번의 상태 코드를 받은 경우에는 이 헤더에 요청에 의해 생성된 리소스 URI가 담겨 있음
- Allow: 허용 가능한 HTTP 메서드
- 405(Method Not Allowed) 응답에 있는 헤더로 사용할 수 있는 메서드 정보를 알려줌
- ex) Allow: GET, HEAD, PUT
- Retry-After: 유저 에이전트가 다음 요청을 하기까지 기다려야 하는 시간
- 503(Service Unavailable) 응답에 있는 헤더로 서비스가 언제까지 불능인지 알려줌
- ex) Retry-After: Fri, 31 Dec 2020 23:59:59 GMT
그리고 독특한 헤더인 콘텐츠 협상 헤더가 있다. 이것은 요청 메시지에서만 사용하는 헤더로 클라이언트에서 선호하는 표현을 전달하는 기능이다. 대표적인 콘텐츠 협상 헤더의 종류는 다음과 같다.
- Accept: 클라이언트가 선호하는 미디어 타입
- Accept-Charset: 클라이언트가 선호하는 문자 인코딩
- Accept-Encoding: 클라이언트가 선호하는 압축 인코딩
- Accept-Language: 클라이언트가 선호하는 자연 언어
사용법은 대체로 비슷한데 이 헤더에 선호하는 종류를 추가하면 된다. 예를 들어 Accept-Language: ko를 추가했다면 서버에서 한국어를 지원한다면 기본 언어 대신 한국어로 응답을 보내게 된다. 한편 서버의 지원 상태를 잘 모르기 때문에 헤더에 여러 값을 넣고 우선순위를 추가해서 요청할 수도 있다. 예를 들어 Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7과 같이 작성할 수 있는데 우선도는 0~1까지의 숫자를 사용해서 표현할 수 있으며 높을수록 우선순위가 높다. 또한 우선순위가 1인 경우는 생략할 수 있으며 따라서 예시에서는 ko-KR이 우선순위가 가장 높고 ko, es-US, en 순이다. 이 중에서 서버에서 지원하는 형식 있다면 우선 순위가 높은 순대로 적용되고 만약 없다면 그냥 서버의 기본 설정으로 응답이 오게 된다.
앞서 URL의 맨 앞 부분에는 통신 프로토콜의 종류이며 HTTPS는 HTTP에서 보안 소켓 계층이나 전송 계층 보안 프로토콜을 추가적으로 사용해서 데이터를 암호화하고 인증할 수 있기 때문에 보안이 강화된 프로토콜 버전이라고 설명했었다. HTTP 프로토콜의 경우 패킷 분석 프로그램으로 보낸 요청을 확인해보면 아이디나 비밀번호와 같이 외부에 노출되면 안되는 데이터도 그대로 확인할 수 있다. 한편 HTTPS 프로토콜로 보낸 요청의 경우 해당 데이터가 암호화되어 전송되기 때문에 외부에서 패킷을 탈취한다고 해도 내용을 알 수 없다.
HTTPS에서 사용하는 보안 프로토콜은 SSL 혹은 TLS 프로토콜인데 SSL이 표준화되면서 바뀐 이름이 TLS라서 사실상 거의 같은 프로토콜이라고 봐도 무방하다. SSL/TLS 프로토콜의 특징을 이야기하자면 CA를 통한 인증서를 사용한다는 것과 대칭 키, 공개 키 암호화 방식을 모두 사용하고 있다는 점이다.
본격적으로 SSL/TLS 프로토콜에 대해 설명하기 전에 키 암호화 방식에 대해 간단하게 소개하자면 자주 사용되는 방식인 대칭 키 암호화 방식과 공개 키 암호화 방식이 있다. 대칭 키 암호화 방식은 하나의 키만 존재하며 데이터를 암호화 하거나 암호화된 데이터를 복호화할 때 동일한 키를 사용한다. 연산 속도가 빠르다는 장점이 있지만 키가 중간에 탈취당할 경우 모든 암호화를 해제할 수 있기 때문에 키 관리가 중요하다. 공개 키 암호화 방식은 두 개의 키를 사용하며 하나의 키로 암호화 했다면 복호화는 다른 키로 진행해야 한다. 이 중 하나를 누구든지 접근이 가능하게 공개해서 공개 키로 만들며 보통 요청을 보내는 사용자들이 이 공개 키를 사용해서 요청을 암호화해서 서버에 보내게 된다. 서버에서는 공개하지 않은 다른 키인 비밀 키를 가지고 요청을 받아서 해당 데이터를 복호화한다. 공개 키는 어차피 이미 모두에게 공개되어 있고 비밀 키는 서버 자체를 해킹하지 않는 이상 탈취할 수 없으므로 보안성 측면에서는 훨씬 좋지만 대칭 키 방식에 비해 수학적 연산 과정이 들어가 있는 등 계산 비용이 대칭 키 방식에 비해 높아서 더 많은 시간이 소모된다는 단점도 있따.
다시 SSL/TLS 프로토콜로 HTTPS를 사용하면 브라우저가 서버의 응답과 함께 전달된 인증서를 확인할 수 있다.
이 인증서는 서버의 신원을 보증해주는 역할을 하며 이런 인증서를 발급해주는 공인 기관을 CA(Certificate Authority)라고 부른다. 서버는 인증서를 발급받기 위해 CA에 서버의 정보와 공개 키를 전달하며 CA에서는 이 정보를 CA가 가진 비밀 키로 암호화해서 인증서를 발급한다.
서버는 클라이언트에게 요청을 받으면 이 인증서를 보내는데 사용자가 사용하는 브라우저에는 CA들의 리스트와 공개 키가 내장되어 있다. 그래서 해당 인증서를 발급한 CA가 브라우저 내장 리스트에 있는지 확인한 뒤에 그 공개 키를 바탕으로 인증서의 복호화를 시도한다. 따라서 인증서에 문제가 없다면 복호화가 성공적으로 진행되서 클라이언트에서도 서버의 정보와 공개 키를 얻을 수 있지만 복호화가 실패한다면 서버가 보내준 인증서에 문제가 있다는 의미이다.
한편 서버의 공개 키를 얻었다고 해서 모든 과정이 끝난 것은 아니다. 공개 키 암호화 방식으로 모든 요청을 암호화하는 것은 위에서 언급했듯이 시간이 더 소모되기 때문에 비효율적이다. 따라서 더 빠른 대칭 키 암호화 방식을 사용하되 이 방식의 문제점인 대칭 키의 탈취 위험을 줄이기 위해 서버의 공개 키가 활용된다. 클라이언트에서 대칭 키를 서버에게 보내면서 이 키를 서버의 공개 키로 암호화해서 보낸다면 비밀 키를 가지고 있는 서버에서만 이것을 복호화할 수 있기 때문에 탈취 위험 없이 대칭 키를 보낼 수 있다.
이제 클라이언트와 서버는 서버 자체를 해킹해서 서버 비밀 키가 노출되지 않는 이상 탈취할 수 없는 같은 대칭 키를 가지게 되었다. 이제 데이터의 암호화 및 복호화는 이 대칭 키를 통해 진행하면 빠르면서도 보안성이 높은 데이터 전송 방식을 구현할 수 있다. 이런 일련의 과정을 SSL/TLS 프로토콜이라고 부르며 HTTP 프로토콜에 이 보안 프로토콜을 추가한 것이 바로 HTTPS 프로토콜인 것이다.