Logo

쿠키 1부: HTTP로 설명하는 쿠키(cookie)

“쿠키는 클라이언트에 저장되고… 음,,, 보안에 좋지 않습니다.” 😅

개발자 면접을 볼 때 쿠키에 대해서 물어보면 가장 흔하게 들을 수 있는 대답인데요. 완전히 틀린 말은 아니지만 뭔가 알맹이가 빠진 느낌이 듭니다.

“그럼 보안을 위해서 쿠키는 안 쓰는 게 좋겠네요?”

라고 반문을 하면 오랫동안 웹 개발을 한 분들도 머뭇거리시는 경우가 많은데요. 아무래도 대부분의 서버 프레임워크에서 쿠키를 직접 다루지 않아도 되도록 추상화 잘 되어 있기 때문에 오히려 쿠키를 직접 다루거나 쿠키에 대해 깊이 생각해볼 기회가 많지 않아서 그런 것 같습니다.

사실 쿠키는 웹에서는 빼놓을 수 없는 핵심적인 기술이죠? 프런트 개발을 하든 백엔드 개발을 하든 웹 개발자로서 쿠키를 제대로 이해해두시면 두고두고 유용한 지식이 될 거라 생각합니다. 그래서 2개의 포스팅에 걸쳐서 쿠키를 기술적으로 깊게 파해쳐보려고 합니다. 🔪

HTTP 쿠키란?

브라우저 쿠키라고도 하고 웹 쿠키라고도 불리는 HTTP 쿠키(cookie)는 이렇게 앞에 붙는 수식어로 유추할 수 있듯이 기본적으로 HTTP 통신을 기반으로 하며 브라우저에서 돌아가는 웹사이트나 웹애플리케이션에서 널리 사용되고 있습니다.

쿠키를 한마디로 정의하면 서버가 어떤 데이터를 브라우저 측에 저장한 후 다시 그 데이터를 받아오는 기술, 또는 그 데이터 자체를 뜻합니다. 따라서 밑에서 더 자세히 설명드리겠지만 쿠키가 제대로 작동하기 위해서는 무엇보다 브라우저의 역할이 가장 중요합니다.

브라우저가 원격에 있는 서버와 네트워크를 통해 쿠키를 주고 받으려면 이 둘 간에는 일정한 약속이 필요하겠죠? 이 약속은 바로 HTTP 프로토콜 상에서 구현되며 쿠키는 HTTP 메시지의 헤더(header) 영역을 통해서 송수신되도록 약속이 되어 있습니다.

하지만 HTTP 헤더는 기본적으로 브라우저 화면에 나타나지 않기 때문에 일반 사용자들은 확인하기가 어려운데요. 그래서 많은 분들이 쿠키를 떠올리면 뭔가 실체가 없고 어디선가 몰래 왔다갔다하는… 그런 막연하게 인식이 생긴 것 같습니다.

쿠키의 매커니즘

쿠키를 기술적으로 제대로 이해하려면 브라우저와 서버가 어떤 과정을 거쳐 쿠키를 주고 받으며 HTTP 통신을 하는지 알아야 합니다.

먼저 쿠키라는 데이터는 어떤 모양일까요? 쿠키는 <이름>=<값> 형태를 지니는 단순한 문자열입니다. 서버와 브라우저는 기본적으로 HTTP 메시지 안에 이 쿠키를 담아서 주고 받게 됩니다.

서버가 어떤 쿠키를 브라우저에 저장을 하고 싶다면 당연히 해당 쿠키를 브라우저에 보내줘야할텐데요. 클라이언트 서버 모델에서는 서버가 클라이언트의 요청없이 클라이언트로 데이터를 보낼 수 없습니다. 따라서 이러한 쿠키 전달 과정은 서버가 클라이언트 요청에 응답할 때 일어나게 됩니다. 서버는 Set-Cookie라는 응답 헤더에 브라우저가 수신해야 할 쿠키 정보를 명시하도록 되어 있습니다.

하나의 Set-Cookie 응답 헤더에는 하나의 쿠키만 담을 수 있어서 여러 개의 쿠키를 보낼 때는 아래와 같은 모습이 됩니다.

HTTP응답
Set-Cookie: <이름>=<>
Set-Cookie: <이름>=<>
Set-Cookie: <이름>=<>

이렇게 서버로 부터 쿠키를 응답 받은 브라우저는 우선 해당 쿠키를 클라이언트 컴퓨터의 하드디스크에 저장합니다. 그리고 브라우저가 동일한 서버에 요청을 할 때 저장해놓은 쿠키를 Cookie라는 요청 헤더에 실어서 돌려 보냅니다.

Cookie 요청 헤더에는 아래와 같은 형식에 따라 여러 개의 쿠키를 ;로 구분하여 나열할 수 있습니다.

HTTP요청
Cookie: <이름>=<>; <이름>=<>; <이름>=<>

여기서 중요한 점은 서버가 Set-Cookie 헤더를 통해 브라우저로 쿠키를 보내는 것은 일회성 작업이지만, 반대로 브라우저가 Cookie 헤더를 통해 서버로 쿠키를 돌려 보내는 것은 일정 시간동안 반복해서 수행되는 작업이라는 것입니다.

쉽게 말해서 서버 입장에서 보면 사실 쿠키는 완전히 개꿀인 기술입니다. 브라우저에게 쿠키 하나만 툭 던져놓으면 🍪 매번 시키지 않아도 브라우저가 스스로 계속해서 쿠키를 들고 오니까요 🚚

사실 이렇게 Set-Cookie 응답 헤더로 넘어온 쿠키를 다시 Cookie 요청 헤더로 돌려보내는 것은 브라우저라는 HTTP 클라이언트만 해주는 매우 독특한 작업입니다. 다시 말해 만약에 curl 커맨드처럼 다른 HTTP 클라이언트 도구를 사용하면 Set-Cookie 응답 헤더는 Content-TypeDate와 같은 다른 평범한 응답 헤더처럼 그 이상의 특별한 의미를 갖지 않게 되는 것이지요. 결론적으로 모든 브라우저가 쿠키를 지원하기 위해서 이러한 일종의 계약(?)을 준수하고 있기 때문에 서버가 브라우저에 쿠키를 저장해놓고 계속해서 받아올 수 있는 것입니다.

쿠키의 송수신 예제

예를 들어, 서버에서 a=1, b=2라는 2개의 쿠키를 브라우저에 저장하고 싶다고 가정해봅까요?

www.test.com 서버는 브라우저의 요청이 들어오면 각 쿠키를 Set-Cookie 헤더에 실어서 응답합니다.

HTTP요청
GET /index.html HTTP/1.1
Host: www.test.com
HTTP응답
HTTP/1.1 200 OK
Content-Type: text/html
Set-Cookie: a=1Set-Cookie: b=2

브라우저는 서버로 부터 받은 이 2개의 쿠키를 클라이언트의 하드디스크에 저장을 해둡니다. 그리고 추후 동일한 서버로 요청을 할 때마다 Cookie 헤더에 이전에 서버로 부터 받았던 쿠키를 그대로 돌려 보냅니다.

HTTP요청
GET /index.html HTTP/1.1
Host: www.test.com
Cookie: a=1; b=2

해당 브라우저는 사용자가 www.test.com이라는 도메인에 머무는 한 /index.html을 방문하든 /about.html을 방문하든 /contact.html을 방문하든 매번 같은 쿠키를 돌려줍니다.

HTTP요청
GET /about.html HTTP/1.1
Host: www.test.com
Cookie: a=1; b=2
HTTP요청
GET /contact.html HTTP/1.1
Host: www.test.com
Cookie: a=1; b=2

이제 서버는 이 브라우저로 부터 들어오는 요청에서 a=1, b=2라는 쿠키를 읽어서 원하는 용도로 활용할 수가 있습니다. 즉, 쿠키라는 매커니즘을 통해서 서버는 데이터를 브라우저에 저장해놨다가 나중에 브라우저에서 요청이 들어올 때 그 데이터를 함께 받아올 수 있는 것이지요.

쿠키의 유효 기간

위에서 브라우저는 일정 시간동안 계속해서 서버로 쿠키를 돌려 보낸다고 설명드렸는데요. 브라우저가 쿠키를 얼마나 오래동안 돌려 보내야하는지는 서버가 맨 처음 쿠키를 보낼 때 결정을 하며, Set-Cookie 응답 헤더를 통해 명시되야 합니다.

유효 기간이 별도로 명시되지 않은 쿠키를 보통 세션 쿠키(session cookie)라고 부르는데 브라우저의 세션이 종료될 때 함께 만료됩니다. 즉, 브라우저의 탭이나 윈도우를 닫으면 서버가 보냈던 쿠키는 모두 만료되어 브라우저는 더 이상 해당 쿠키를 서버에 돌려 보내지 않습니다.

반면에 유효 기간이 명시되어 있는 쿠키인 영속 쿠키(permanent cookie)는 세션과 무방하게 특정 기간이나 특정 시점까지 유효합니다. 쿠키의 유효 기간을 명시할 때는 Set-Cookie 응답 헤더에 Expires 속성이나 Max-Age 속성을 다음과 같은 형태로 사용합니다.

HTTP응답
Set-Cookie: <쿠키 이름>=<쿠키 값>; Expires=종료 시점
Set-Cookie: <쿠키 이름>=<쿠키 값>; Max-Age=유효 기간

Max-Age 속성은 초 단위로 설정하며, Expires 속성과 Max-Age 속성이 둘 다 있을 경우 Max-Age 속성이 우선합니다. 둘 가지 속성이 모두 없을 때 해당 쿠키는 세션이 종료될 때 만료되는 세션 쿠키가 됩니다.

참고로 브라우저에 저장해두었던 쿠키를 즉시 만료하고 싶을 때는 다음과 같이 Max-Age 속성을 0으로 설정해주면 되겠습니다.

HTTP응답
Set-Cookie: a=1; Max-Age=0

쿠키의 적용 범위

브라우저가 기본적으로 쿠키를 보낸 서버가 속한 도메인으로만 쿠키를 되돌려 보내지만 어떤 URL을 방문할 때 해당 쿠키를 보내야할지 말지를 좀 더 세밀하게 제어할 수도 있습니다.

Set-Cookie 응답 헤더에 Domain 속성을 명시하면, 서브 도메인까지 포함되도록 쿠키의 범위가 확장됩니다.

HTTP응답
Set-Cookie: <쿠키 이름>=<쿠키 값>; Domain=도메인

예를 들어, Domain 속성을 test.com으로 설정하면 브라우저는 a.test.com으로 부터 받은 쿠키를, b.test.com으로도 보내게 됩니다. 그러므로 a.test.comb.test.com이 쿠키를 공유하는 효과가 발생하게 됩니다.

Set-Cookie 응답 헤더에 Path 속성을 명시하면 쿠키의 범위를 해당 도메인의 특정 경로로 쿠키의 범위를 축소시킬 수 있습니다.

HTTP응답
Set-Cookie: <쿠키 이름>=<쿠키 값>; Path=경로

예를 들어, Path 속성이 /users라고 설정되어 있는 쿠키는, 브라우저가 /users를 포함한 하위 경로로 요청을 할 때만 서버로 돌려 보냅니다.

쿠키가 왜 필요하게 되었을까?

이렇게 쭉 살펴보니 쿠키가 생각했던 것보다 기술적으로 상당히 복잡하죠? 😅

여기서 많은 분들이 뭐하러 서버는 이렇게 수고를 들여 데이터를 굳이 브라우저에 저장하려고 하는지 의아해하실 것 같습니다. 그냥 데이터를 싸그리 서버에 저장해두면 훨씬 편할 것 같은데 말이죠…

요즘처럼 서버 장비의 가격이 저렴하고 심지어 클라우드 인프라까지 이용할 수 있는 환경에서는 이해가 어렵겠지만, 서버 측에서 많은 데이터를 저장하는 것이 부담스러울 정도로 하드웨어의 가격이 비싸던 시절로 돌아가보면 어떨까요?

예를 들어, 사용자 한 명당 저장해야할 데이터가 1KB라고 가정하면, 사용자 백만명에게 서비스하려면 서버에 1GB의 저장 공간이 필요했을 것입니다. 지금이야 일반 노트북에도 수백 GB의 하드디스크가 장착되어 있지만, 인터넷이 태동한 90년대에 이러한 데이터를 서버가 오로지 감당하기는 벅찼을 것입니다.

하지만 데이터 저장에 대한 부담을 클라이언트에 전가할 수 있다면 어떨까요? 당시에도 브라우저를 설치할 수 있을 정도의 PC라면 1KB 정도는 충분히 감당할 수 있는 저장공간이 있었을 것입니다. 따라서 예전에는 이러한 제약사항 때문에 클라이언트 측에 데이터를 저장할 요구가 컸었고 자연스럽게 브라우저에 데이터를 저장하기 위한 쿠키라는 것이 고안되었던 것이지요.

쿠키의 한계

쿠키가 브라우저, 즉 클라이언트 측에 저장된다라는 것은 상당히 널리 알려진 사실인데요. 이 부분은 쿠키가 탄생하게 된 중요한 배경이며, 안타깝게도 갖가지 이슈의 원흉이기도 합니다.

따라서 쿠키를 사용할 때는 아래와 같은 쿠키의 한계점들을 잘 인지하고 사용하는 것이 중요하겠습니다.

첫째, 쿠키는 유실되기 쉽습니다.

대부분 브라우저는 환경 설정에서 쿠키 일괄 삭제 기능을 제공하며, 웹사이트 별로도 어렵지 않게 쿠키를 삭제할 수 있습니다. 또한 특정 브라우저가 하드디스크의 어느 경로에 쿠키를 저장하는지는 구글링을 해보면 누구나 쉽게 파악할 수 있어서 통째로 폴더를 삭제해버릴 수도 있습니다. 그러므로 유실되면 안 되는 중요한 데이터는 쿠키를 사용해서 브라우저에 저장하면 안 되겠습니다.

둘째, 쿠키는 변조되기 쉽습니다.

브라우저의 개발자 도구를 사용하면 각 웹사이트 별로 현재 어떤 쿠키가 저장되어 있는지 한 눈에 파악할 수 있으며 쿠키를 손쉽게 변경할 수 있습니다. 이를 통해 서버가 브라우저로 보낸 쿠키와 전혀 다른 쿠키를 서버에 돌려 보낼 수도 있으며, 심지어 서버가 저장한 적이 없는 새로운 쿠키를 만들어서 서버에 보낼 수도 있습니다. 뿐만 아니라 프록시 서버를 이용해서 브라우저에서 보낸 쿠키를 중간에서 변조해서 서버에 보내는 것도 충분히 가능한 일 입니다. 따라서 서버에서는 브라우저로 부터 수신한 쿠키 데이터가 유효한지 검증할 필요가 있습니다.

둘째, 쿠키는 도난되기 쉽습니다.

클라이언트의 컴퓨터에 저장되는 쿠키는 항상 온갖 해킹의 위험에 노출되어 있습니다. 아무리 클라이언트 단에서 개개인이 보안에 신경을 쓴다고 해도 기업이 소유하고 있는 서버 단의 보안 수준에 비할 바가 못되겠죠. 클라이언트에 저장되어 있는 쿠키가 탈취되는 것을 서버 입장에서 막을 도리는 없을 것입니다. 따라서 개인 정보와 같이 민감한 데이터를 쿠키를 사용해서 저장하면 곤란한 일이 벌어질 수도 있습니다.

이러한 쿠키의 한계를 깨닫게 되면 사실 쿠키로 저장할 만한 데이터가 딱히 없다고 느껴질 것입니다. 요즘 같이 데이터가 중요한 시대에 유실되거나 조작되거나 도난되어도 무방한 데이터가 얼마나 될까요?

쿠키의 대체 기술

서버 자원이 풍족한 현재에는 순수하게 데이터를 클라이언트 측에 저장하기 위한 용도로써의 쿠키는 입지가 많이 좁아진 것이 사실입니다. 대부분의 모던 브라우저는 로컬 스토리지나 세션 스토리지와 같은 쿠키를 대체할 수 있는 웹 스토리지 기술을 지원하고 있기 때문입니다.

로컬 스토리자나 세션 스토리지와 같은 웹 스토리지 기술이 궁금하신 분들은 관련 포스팅을 참고 바라겠습니다.

게다가 웹 개발의 페러다임이 백엔드에서 프론트엔드로 옮겨오면서 예전보다 많은 코드가 서버가 아닌 브라우저에서 실행되고 있습니다. 그래서 굳이 브라우저에 저장되어 있는 데이터를 서버로 전송하지 않고도 자바스크립트로 해당 데이터에 쉽게 접근할 수 있게 되었습니다.

이러한 웹 스토리지 기술 대비 쿠키의 최대 단점은 매 요청마다 같은 데이터가 서버로 전송됨에 따른 네트워크 대역폭 낭비입니다. 따라서 구지 서버 단에서 읽을 필요가 없이 순수하게 브라우저에 저장해도 무방한 데이터라면 굳이 쿠키를 통해 네트워크를 통해 주고 받을 필요가 없겠습니다.

쿠키의 보안 속성

이러한 쿠키의 한계와 대체 기술에도 불구하고 반드시 쿠키를 사용해야 하는 상황이라면 가급적 보안 속성을 사용하시기를 권장드립니다.

첫번째 보안 속성은 Secure인데요. Set-Cookie 응답 헤더에 이 속성이 명시된 쿠키는 브라우저가 https 프로토콜 상에서만 서버로 돌려 보냅니다. 네트워크 상에서 탈취되었을 때 문제가 될 수 있는 쿠키를 상대로 쓰면 유용할 것입니다.

HTTP응답
Set-Cookie: <쿠키 이름>=<쿠키 값>; Secure

두번째 보안 속성은 HttpOnly인데요. Set-Cookie 응답 헤더에 이 속성이 명시된 쿠키는 이 속성이 명시된 쿠키는 브라우저에서 자바스크립트로 Document.cookie 객체를 통해 접근할 수 없습니다. 서드 파티(third party) 자바스크립트 코드가 쿠키에 접근하는 것을 제한할 수 있습니다.

HTTP응답
Set-Cookie: <쿠키 이름>=<쿠키 값>; HttpOnly

마치면서

지금까지 쿠키가 어떻게 서버와 브라우저 간에 이동을 하는지, 그리고 쿠키의 유효 기간과 적용 범위를 설정하는 방법과, 쿠키의 한계와 대체 기술에 대해서도 살펴보았습니다.

보통 쿠키에 대해서 논할 때 서버가 데이터를 브라우저에 저장할 수 있다는 측면에 초첨을 많이 맞춰지는데요. 저는 개인적으로 쿠키에서 좀 더 중요하게 다뤄줘야 하는 측면은 쿠키의 지속성에 있다고 생각합니다.

사실 이것은 쿠키의 치명적인 단점에도 불구하고 아직까지 웹에서 살아남아 활발하게 사용되는 이유이기도 한데요. 이 부분은 사용자 인증과도 밀접한 관련이 있는 부분이라서 다음 포스팅에서 자세히 다루도록 하겠습니다.