안녕하세요, 장동호 입니다!
오늘은 HTTP의 한계를 극복하기 위한 다양한 기술들을 알아보겠습니다.
HTTP의 특징
우선 HTTP 기반의 다양한 기술들을 알아보기 전에 먼저 HTTP 프로토콜의 특징을 정리하고 넘어가겠습니다.
HTTP는 기본적으로 스테이트리스(stateless) 프로토콜입니다. 즉, 모든 요청은 독립적이며 서버는 이전 요청의 상태를 기억하지 않습니다.
이 덕분에 서버 입장에서는 매 요청마다 새로운 클라이언트라고 가정하고 처리할 수 있어 리소스를 절약할 수 있지만, 클라이언트 입장에서는 사용자 상태(예: 로그인 정보)를 유지하려면 추가적인 방법이 필요합니다.
예를 들어, 사용자가 로그인하고 나서 다시 방문했을 때 로그인 상태가 유지되는 경험을 제공하려면 어떻게 해야 할까요?
이러한 HTTP의 한계를 보완하기 위해 등장한 것이 바로 쿠키와 세션, 그리고 최근에는 웹 스토리지입니다.
쿠키(Cookie)
쿠키는 클라이언트(브라우저)에 저장되는 작은 데이터 조각입니다.
서버가 사용자의 브라우저에게 특정 정보를 저장하도록 명령하면, 브라우저는 해당 정보를 쿠키에 저장하고 다음 요청부터 자동으로 서버에 전송합니다.
동작 방식
1.
사용자가 로그인하면, 서버는 응답에 Set-Cookie 헤더로 쿠키를 보냅니다.
2.
이후 사용자의 브라우저는 모든 요청에 이 쿠키를 Cookie 헤더로 자동 포함합니다.
3.
서버는 쿠키 정보를 통해 “아, 이건 로그인한 사용자구나!” 하고 판단합니다.
웹 스토리지(Web Storage)
쿠키 외에도 HTML5에서 등장한 웹 스토리지는 클라이언트에 데이터를 저장하는 또 다른 방식입니다.
쿠키보다 저장 용량이 크고, 서버에 전송되지 않아 성능적으로 유리한 점이 있습니다.
웹 스토리지의 종류
종류 | 설명 | 지속 시간 |
localStorage | 브라우저에 영구 저장 | 브라우저 종료해도 유지 |
sessionStorage | 탭/세션 단위 저장 | 탭 종료 시 삭제 |
세션(Session)
세션은 쿠키와 마찬가지로 사용자의 상태를 유지하기 위한 방법이지만, 데이터를 클라이언트가 아닌 서버에 저장한다는 점이 가장 큰 차이점입니다.
즉, 사용자가 로그인하면 서버는 해당 사용자에 대한 정보를 메모리나 DB에 저장하고, 클라이언트에게는 세션 ID만 쿠키 형태로 전달합니다. 클라이언트는 이후 요청마다 이 세션 ID를 서버에 보내고, 서버는 이 ID를 통해 어떤 사용자인지 식별할 수 있습니다.
동작 방식
1.
클라이언트가 로그인 요청
2.
서버가 사용자 정보를 저장하고, 고유한 세션 ID 생성
3.
Set-Cookie를 통해 세션 ID를 브라우저에 저장
4.
클라이언트는 이후 요청마다 Cookie: session_id=xyz123와 함께 요청
5.
서버는 xyz123에 해당하는 사용자 정보를 찾아 요청 처리
정리하면 쿠키는 클라이언트 중심, 세션은 서버 중심입니다.
일반적으로 인증 시스템에서는 쿠키와 세션을 함께 사용합니다.
즉, 서버는 로그인한 사용자 정보를 세션에 저장하고, 클라이언트는 세션 ID를 쿠키에 저장해 자동으로 전송함으로써 인증된 상태를 유지하게 되는 것입니다.
구현이 간단하고 서버가 직접 사용자 상태를 관리하므로 제어가 용이하다는 장점이 있지만, 서버 메모리나 저장소에 세션이 쌓이면 규모가 커질수록 부담이 된다는 단점이 있습니다. JWT처럼 서버에 상태를 저장하지 않는 인증 방식도 있지만, 이 부분은 다른 글에서 다루겠습니다.
캐시(Cache)
캐시는 서버로부터 받은 응답 데이터 사본을 임시 저장해두고 재사용하는 메커니즘입니다. 이 캐시 기능을 활용하면 같은 요청에 대해 서버와의 통신 없이 빠르게 응답할 수 있어 속도 향상과 서버 부하 감소 효과를 얻을 수 있습니다.
저장 위치
위치 | 설명 |
브라우저 캐시 | 클라이언트 브라우저가 로컬에 저장 (가장 빠름) |
프록시 캐시 | CDN이나 중간 서버가 리소스를 저장 |
서버 캐시 | 서버 내부에서 반복적인 계산 결과를 저장 (Reverse Proxy, Redis 등) |
캐시 신선도
만약 서버의 데이터가 바뀌면, 캐시에 저장된 오래된 정보는 어떻게 될까요?
바로 이 문제를 해결하기 위해 캐시 신선도라는 개념이 등장합니다.
HTTP 캐시에서 신선도란 캐시된 응답이 ‘여전히 유효한지’ 판단하는 기준입니다.
HTTP 응답 헤더에서는 다음과 같은 정보를 바탕으로 신선도를 판단합니다.
1.
Cache-Control: max-age
•
응답이 저장된 시점부터 최대 몇 초 동안 유효한지 명시
•
예: max-age=600 → 저장된 지 10분 이내면 신선함
2.
Expires
•
유효한 만료 시점을 절대 시간으로 지정
•
예: Expires: Wed, 29 May 2025 12:00:00 GMT
•
현재는 Cache-Control이 우선 적용됨
캐시 신선도 재검사
캐시가 만료되어 신선하지 않다고 판단되면, 클라이언트는 서버에 조건부 요청을 보내 최신 데이터를 확인합니다.
조건부 요청에는 두 가지 대표적인 방식이 있습니다.
1. If-Modified-Since 헤더 (Last-Modified 활용)
•
클라이언트가 마지막으로 받은 리소스가 언제 수정되었는지 서버에 알려줍니다.
•
서버는 해당 리소스가 그 이후 변경되었는지 확인합니다.
•
변경 없으면 304 Not Modified를 보내 클라이언트는 기존 캐시를 그대로 사용합니다.
•
변경됐다면 최신 리소스를 다시 보냅니다.
다만, 시간 단위가 초 단위라 아주 자주 수정되는 경우 정확도가 떨어질 수 있습니다.
2. If-None-Match 헤더 (ETag 활용)
•
서버는 리소스마다 고유한 Etag(식별 태그)를 만들어 응답에 포함합니다.
•
클라이언트는 ETag를 저장해두고, 다음 요청 때 If-None-Match 헤더로 서버에 보냅니다.
•
서버가 태그를 비교해 같으면 304 Not Modified, 다르면 최신 리소스를 전달합니다.
ETag는 리소스 변경을 더 정확하게 판단할 수 있어, 최근에는 이 방식이 더 널리 쓰입니다.
캐시 응답
HTTP 캐시에서 서버가 클라이언트로부터 조건부 요청을 받았을 때, 서버는 요청받은 자원의 상태에 따라 서로 다른 응답을 보냅니다.
먼저, 요청받은 자원이 서버에서 변경된 경우입니다. 클라이언트는 이전에 받은 자원의 마지막 수정 시간이나 ETag 값을 조건부 요청 헤더에 담아 서버에 보냅니다. 서버는 이를 확인한 뒤 자원이 변경되었다고 판단하면, 최신 데이터를 포함한 200 OK 응답을 클라이언트에 전달합니다. 이때 클라이언트는 서버가 보낸 최신 데이터를 받아 캐시를 갱신하고 화면에 보여주게 됩니다.
반면, 요청받은 자원이 변경되지 않은 경우에는 서버가 데이터를 다시 전송하지 않습니다. 대신, 304 Not Modified 상태 코드를 응답으로 보내 클라이언트에게 기존에 캐시된 데이터를 그대로 사용해도 된다는 신호를 줍니다. 클라이언트는 서버로부터 추가 데이터 없이도 기존 캐시를 이용해 빠르게 페이지를 렌더링할 수 있으며, 이로 인해 네트워크 트래픽과 응답 시간이 크게 줄어듭니다.
마지막으로, 요청받은 자원이 서버에서 삭제된 경우입니다. 서버에 더 이상 해당 자원이 존재하지 않는다면, 일반적으로 404 Not Found 응답을 보내게 됩니다. 이 경우 클라이언트는 이전에 캐시해둔 데이터가 더 이상 유효하지 않다는 것을 인지하고 캐시를 삭제하거나 무시하며, 사용자에게 적절한 오류 메시지를 보여주는 처리가 필요합니다.
보안: SSL/TLS와 HTTPS
인터넷은 기본적으로 공개된 네트워크입니다. 우리가 웹사이트에 로그인하거나, 결제할 때 주고받는 데이터가 중간에 누군가에게 가로채이거나 변조된다면 큰 문제가 생깁니다. 그래서 데이터를 안전하게 주고받기 위한 보안 기술이 필요합니다.
SSL(Secure Sockets Layer)과 TLS(Transport Layer Security)는 웹에서 데이터를 암호화해서 안전하게 전송하기 위한 프로토콜입니다. 쉽게 말해, 데이터를 ‘잠금 장치’로 감싸서 중간에 누군가 훔쳐보거나 수정하지 못하게 만드는 기술입니다.
HTTPS(HyperText Transfer Protocol Secure)는 HTTP에 SSL/TLS를 더해 안전성을 더한 프로토콜입니다. TLS의 동작을 통해 HTTPS의 동작 과정을 이해해보겠습니다. TLS 기반 HTTPS 메시지는 크게 다음과 같은 단계를 거쳐 송수신됩니다.
1.
TCP 쓰리 웨이 핸드셰이크
2.
TLS 핸드셰이크
3.
메시지 송수신
HTTPS 메시지 송수신은 일반적인 HTTP 메시지 송수신에 2가 더해진 것에 불과합니다. 2의 과정을 거쳐 메시지 암호화가 이루어지므로 2를 거쳐 3에서 주고받는 메시지는 암호화된 메시지입니다.
2의 TLS 핸드셰이크는 다음과 같은 메시지로 주고받는 과정을 의미합니다. 다음은 TLS 핸드셰이크 과정에서 주고받는 주요 메시지로, 그림 속 메시지를 주고받으면 인증과 암호화가 이루어진다고 이해하면 됩니다. 여기에서 녹색으로 표시한 주요 메시지, ClientHello와 ServerHello, Certificate, Certificateverify, Finished 메시지 위주로 학습해 보겠습니다.
이미지 출처: https://csnote.net/
TLS 핸드셰이크
우리가 알아 두어야 하는 TLS 핸드셰이크의 핵심 내용은 크게 2가지입니다. 하나는 TLS 핸드셰이크를 통해 암호화 통신을 위한 키를 생성/교환할 수 있다는 점이고, 또 하나는 인증서 송수신과 검증이 이루어질 수 있다는 점입니다.
‘암호화 통신을 위한 키’란 무엇일까요? 암호화를 수행하는 알고리즘은 암호화 알고리즘이라고 합니다. 그리고 TLS에서 활용되는 암호화 알고리즘을 통해 평문을 암호화하거나 반대로 암호문을 복호화하려면 키(key)라는 정보가 필요합니다. 키는 암호화 통신을 수행하는 두 호스트만 알고 있어야 하는 정보로, TLS 핸드셰이크 과정에서 ClientHello 메시지, ServerHello 메시지를 주고받으며 생성/교환됩니다.
앞쪽에서 다룬 TLS 핸드셰이크 그림을 다시 확인해보겠습니다. 처음으로 클라이언트는 ClientHello 메시지를 보냅니다. 이 메시지는 암호화된 통신을 위해 서로 맞춰 봐야 할 정보들을 제시하는 메시지입니다. 지원되는 TLS 버전, 사용 가능한 암호화 알고리즘과 해시 함수, 키를 만들기 위해 사용할 클라이언트의 난수 등이 포함되어 있습니다. 이때 클라이언트는 ‘사용 가능한 암호화 알고리즘과 해시 함수’를 서버에 알리기 위해 Client 메시지에 다음과 같은 형태의 정보를 포함하여 전송합니다. 이를 암호 스위트(cipher suite)라고 합니다. 한 줄 한 줄이 암호화 알고리즘의 해시 함수의 종류를 나타냅니다.
TLS_AES_128_GCM_SHA256
TLS_AES_256_GCM_SHA384
TLS_CHACHA20_POLY1305_SHA256
TLS_AES_128_CCM_SHA256
TLS_AES_128_CCM_8_SHA256
Plain Text
복사
서버는 ClientHello 메시지에 대한 응답으로 ServerHello 메시지를 전송합니다. ClientHello 메시지가 암호화 이전에 맞춰 봐야 할 정보들을 제시하는 메시지라면, ServerHello 메시지는 제시된 정보들을 선택하는 메시지입니다. 따라서 이 메시지에는 선택된 TLS 버전, 암호 스위트 등의 정보, 키를 만들기 위해 사용할 서버의 난수 등이 포함되어 있습니다. ClientHello 메시지와 ServerHello 메시지를 주고받으면 암호화된 통신을 위해 사전 협의해야 할 정보들이 결정되고, 결정된 정보를 토대로 서버와 클라이언트가 암호화에 사용할 키를 만들어 암호화에 사용할 수 있습니다.
TLS 핸드셰이크에서는 암호화 통신을 위한 키의 교환도 이루어지지만, 인증서의 송수신과 검증도 이루어질 수 있다고 언급했습니다. 이를 이해하기 위해서는 우선 인증서(공개 키 인증서)가 무엇인지부터 이해해야 합니다. 가령 크롬 웹 브라우저를 통해 HTTPS를 사용하는 사이트에 접속한 뒤, 다음과 같이 [이 연결은 안전합니다] - [인증서가 유효함]을 클릭해 보세요.
[인증서가 유효함]을 클릭하면 다음과 같은 [인증서 뷰어] 창이 열립니다. 이것이 바로 인증서입니다.
인증서(certificate)는 ‘당신이 통신을 주고받는 상대방은 틀림없이 당신이 의도한 대상이 맞다’라는 사실을 입증하기 위한 정보입니다.
그런데 송수신하는 당사자가 ‘여러분이 메시지를 주고받는 대상은 틀림없이 내가 맞다’는 사실을 보장하면 아무런 의미가 없겠죠. 제3의 기관이 이를 보장해야 합니다. 그래서 인증서를 발급하고 검증하는 제3의 인증기관(CA)이 있습니다. 이 인증기관은 인증서의 발급과 검증, 저장 등의 역할을 수행하는 공인 기관입니다.
다시 TLS 이야기로 돌아와, 인증서 및 인증서 검증과 관련한 메시지로는 Certificate 메시지와 CertificateVerify 메시지가 있습니다. Certificate 메시지에는 인증서 서명 값 등 앞서 제시한 그림과 같은 인증서 내용들이 포함되어 있으며, CertificateVerify 메시지는 인증서의 내용이 올바른지 검증하기 위한 메시지입니다.
이렇게 서버와 클라이언트는 암호화에 사용할 키를 획득하고, 서로가 틀림없다는 사실까지 인증했습니다. 서버와 클라이언트는 마지막으로 TLS 핸드셰이크의 마지막을 의미하는 Finished 메시지를 주고받고, 이후부터는 TLS 핸드셰이크를 통해 얻어낸 키를 바탕으로 암호화된 데이터를 주고받게 됩니다.