그럼에도 불구하고

👨‍💻

[JavaScript] Cookie란? 본문

JavaScript/JavaScript basics

[JavaScript] Cookie란?

zenghyun 2023. 4. 10. 21:23

오늘은 Cookie에 대해 알아보겠습니다.

 

[ Cookie ] 

쿠키는 브라우저에 저장되는 작은 크기의 문자열로 HTTP 프로토콜의 일부입니다. 

 

쿠키는 주로 웹 서버에 의해 만들어집니다. 서버가 HTTP 응답 헤더(header)의 Set-Cookie에 내용을 넣어 전달하면, 브라우저는 이 내용을 자체적으로 브라우저에 저장합니다. 브라우저는 사용자가 쿠키를 생성하도록 한 동일 서버(사이트)에 접속할 때마다 쿠키의 내용을 Cookie 요청 헤더에 넣어서 함께 전달합니다.

쿠키는 클라이언트 식별과 같은 인증에 가장 많이 쓰입니다.

 

  1. 사용자가 로그인하면 서버는 HTTP 응답 헤더의 Set-Cookie에 담긴 “세션 식별자(session identifier)” 정보를 사용해 쿠키를 설정합니다.
  2. 사용자가 동일 도메인에 접속하려고 하면 브라우저는 HTTP Cookie 헤더에 인증 정보가 담긴 고윳값(세션 식별자)을 함께 실어 서버에 요청을 보냅니다.
  3. 서버는 브라우저가 보낸 요청 헤더의 세션 식별자를 읽어 사용자를 식별합니다.

document.cookie 프로퍼티를 이용하면 브라우저에서도 쿠키에 접근할 수 있습니다.

쿠키와 다양한 쿠키의 옵션을 다루는 일은 쉽지 않습니다.

 

 

[ 쿠키 읽기 ]

document.cookie;

 

document.cookie는 name=value쌍으로 구성되어 있고, 각 쌍은 ;로 구분합니다. 이때, 쌍 하나는 하나의 독립된 쿠키를 나타냅니다.

;를 기준으로 document.cookie의 값을 분리하면 원하는 쿠키를 찾을 수 있습니다.  

 

 

[ 쿠키 쓰기 ]

document.cookie에 직접 값을 쓸 수 있습니다. 이때 cookie는 데이터 프로퍼티가 아닌 접근자(accessor) 프로퍼티입니다. 

document.cookie에 값을 할당하면, 브라우저는 이 값을 받아 해당 쿠키를 갱신합니다. 이때, 다른 쿠키의 값은 변경되지 않습니다.

아래와 같이 코드를 작성하면 이름이 user 쿠키를 찾아 값을 John으로 갱신합니다.

 

document.cookie = 'user=John'; // 이름이 'user'인 쿠키의 값만 갱신함
alert(document.cookie);

 

코드를 실행하면 여러 개의 쿠키가 출력되는 것을 확인할 수 있습니다. 이를 통해 document.cookie= 연산은 모든 쿠키를 덮어쓰지 않고, 명시된 쿠키인 user의 값만 갱신한 것을 알 수 있습니다.

 

쿠키의 이름과 값엔 특별한 제약이 없기 때문에 모든 글자가 허용됩니다. 하지만 형식의 유효성을 일관성 있게 유지하기 위해 반드시 내장 함수 encodeURIComponent 사용하여 이름과 값을 이스케이프 처리해 줘야 합니다.

 

// 특수 값(공백)은 인코딩 처리해 줘야 합니다.
let name = "my name";
let value = "John Smith"

// 인코딩 처리를 해, 쿠키를 my%20name=John%20Smith 로 변경하였습니다.
document.cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value);

alert(document.cookie); // ...; my%20name=John%20Smith

 

 

📌 쿠키의 한계

 

쿠키에는 몇  가지 제약 사항이 있습니다.

 

1. encodeURIComponent로 인코딩한 이후의 name=value 쌍은 4KB를 넘을 수 없습니다. 이 용량을 넘는 정보는 쿠키에 저장할 수 없습니다.

 

2. 도메인 하나당 저장할 수 있는 쿠키의 개수는 20여 개 정도로 한정되어 있습니다. 개수는 브라우저에 따라 조금씩 다릅니다.

 

 

[ 쿠키 옵션 ]

쿠키에는 몇가지 옵션이 있습니다. 몇몇 옵션은 아주 중요하기 때문에 꼭 지정해줘야 합니다.

옵션은 key=value 뒤에 나열하고 ;로 구분합니다. 

 

document.cookie = "user=John; path=/; expires=Tue, 19 Jan 2038 03:14:07 GMT"

 

 

💡 path

path=/mypath

URL path(경로)의 접두사로, 이 경로나 이 경로의 하위 경로에 있는 페이지만 쿠키에 접근할 수 있습니다. 절대 경로이어야 하고, (미 지정 시) 기본값은 현재 경로입니다. 

 

path=/admin 옵션을 사용하여 설정한 쿠키는 /admin과 /admin/something에선 볼 수 있지만, /home 이나 /adminpage에선 볼 수 없습니다. 

 

특별한 경우가 아니라면, path 옵션을 path=/ 같이 루트로 설정해 웹사이트의 모든 페이지에서 쿠키에 접근할 수 있도록 하는게 좋습니다.

 

 

💡 domain

domain=site.com

 

쿠키에 접근 가능한 domain(도메인)을 지정합니다. 다만, 몇 가지 제약이 있어서 아무 도메인이나 지정할 수 없습니다.

domain 옵션에 아무 값도 넣지 않았다면, 쿠키를 설정한 도메인에서만 쿠키에 접근할 수 있습니다. site.com에서 설정한 쿠키는 other.com에서 얻을 수 없죠.

이 외에 까다로운 제약사항이 하나 더 있습니다. 서브 도메인(subdomain)인 forum.site.com에서도 쿠키 정보를 얻을 수 없다는 점입니다.

 

// site.com에서 쿠키를 설정함
document.cookie = "user=John"

// site.com의 서브도메인인 forum.site.com에서 user 쿠키에 접근하려 함
alert(document.cookie); // 찾을 수 없음

서브 도메인이나 다른 도메인에서 쿠키에 접속할 방법은 없습니다. site.com에서 생성한 쿠키를 other.com에선 절대 전송받을 수 없습니다.

이런 제약사항은 안정성을 높이기 위해 만들어졌습니다. 민감한 데이터가 저장된 쿠키는 관련 페이지에서만 볼 수 있도록 하기 위함입니다. 

 

그런데 정말 forum.site.com 같은 서브 도메인에서 site.com에서 생성한 쿠키 정보를 얻을 방법이 없는 걸까요? 방법이 있습니다. site.com에서 쿠키를 설정할  domain 옵션에 루트 도메인인 domain=site.com 명시적으로 설정해 주면 됩니다. 

 

// site.com에서
// 서브 도메인(*.site.com) 어디서든 쿠키에 접속하게 설정할 수 있습니다.
document.cookie = "user=John; domain=site.com"

// 이렇게 설정하면

// forum.site.com와 같은 서브도메인에서도 쿠키 정보를 얻을 수 있습니다.
alert(document.cookie); // user=John 쿠키를 확인할 수 있습니다.

 

하위 호환성 유지를 위해 (site.com 앞에 점을 붙인) domain=.site.com도 domain=site.com과 동일하게 작동합니다. 오래된 표기법이긴 하지만 구식 브라우저를 지원하려면 이 표기법을 사용하는 것이 좋습니다.

이렇게 domain 옵션값을 적절히 사용하면 서브 도메인에서도 쿠키에 접근할 있습니다.

 

 

 

💡 expires와 max-age

expires(유효 일자)나 max-age(만료 기간) 옵션이 지정되어있지 않으면, 브라우저가 닫힐 때 쿠키도 함께 삭제됩니다. 이런 쿠키를 "세션 쿠키(session cookie)"라고 부릅니다.

expires 나 max-age 옵션을 설정하면 브라우저를 닫아도 쿠키가 삭제되지 않습니다.

expires=Tue, 19 Jan 2038 03:14:07 GMT

브라우저는 설정된 유효 일자까지 쿠키를 유지하다가, 해당 일자가 도달하면 쿠키를 자동으로 삭제합니다.

쿠키의 유효 일자는 반드시 GMT(Greenwich Mean Time) 포맷으로 설정해야 합니다. date.toUTCString을 사용하면 해당 포맷으로 쉽게 변경할 수 있습니다. 아래는 유효 기간이 하루인 쿠키를 만드는 예시입니다.

 

// 지금으로부터 하루 후
let date = new Date(Date.now() + 86400e3);
date = date.toUTCString();
document.cookie = "user=John; expires=" + date;

 

옵션값을 과거로 지정하면 쿠키는 삭제됩니다.

max-age=3600

max-age는 expires 옵션의 대안으로, 쿠키 만료 기간을 설정할 수 있게 해줍니다. 현재부터 설정하고자 하는 만료일시까지의 시간을 초로 환산한 값을 설정합니다.

 

0이나 음수값을 설정하면 쿠키는 바로 삭제됩니다.

 

// 1시간 뒤에 쿠키가 삭제됩니다.
document.cookie = "user=John; max-age=3600";

// 만료 기간을 0으로 지정하여 쿠키를 바로 삭제함
document.cookie = "user=John; max-age=0";

 

 

 

💡 secure

이 옵션을 설정하면 HTTPS로 통신하는 경우에만 쿠키가 전송됩니다.

secure 옵션이 없으면 기본 설정이 적용되어 http://site.com에서 설정(생성)한 쿠키를 https://site.com에서 읽을 수 있고, https://site.com에서 설정(생성)한 쿠키도 http://site.com에서 읽을 수 있습니다.

쿠키는 기본적으로 도메인만 확인하지 프로토콜을 따지진 않기 때문입니다.

하지만 secure 옵션이 설정된 경우, https://site.com에서 설정한 쿠키는 http://site.com에서 접근할 수 없습니다. 쿠키에 민감한 내용이 저장되어 있어 암호화되지 않은 HTTP 연결을 통해 전달되는 걸 원치 않는다면 이 옵션을 사용하면 됩니다.

 

// (https:// 로 통신하고 있다고 가정 중)
// 설정한 쿠키는 HTTPS 통신시에만 접근할 수 있음
document.cookie = "user=John; secure";

 

 

 

💡 samesite

또 다른 보안 속성인 samesite 옵션은 크로스 사이트 요청 위조(cross-site request forgery, XSRF) 공격을 막기 위해 만들어진 옵션입니다.

아래 XSRF 공격 시나리오를 통해 속성의 동작 방식과 언제 속성을 유용하게 사용할 있는지 알아보도록 합시다.

 

 

📌 XSRF 공격 

현재 bank.com에 로그인되어있다고 가정해 봅시다. 해당 사이트에서 사용되는 인증 쿠키가 브라우저에 저장되고, 브라우저는 bank.com에 요청을 보낼 때마다 인증 쿠키를 함께 전송할 것입니다. 서버는 전송받은 쿠키를 이용해 사용자를 식별하고, 보안이 필요한 재정 거래를 처리합니다.

이제 (로그아웃하지 않고) 다른 창을 띄워서 웹 서핑을 하던 도중에 뜻하지 않게 evil.com에 접속했다 가정해 봅시다. 이 사이트엔 해커에게 송금을 요청하는 폼(form) 

이 있고, 이 폼은 자동으로 제출되도록 설정되어 있습니다.

폼이 evil.com에서 은행 사이트로 바로 전송될 인증 쿠키도 함께 전송됩니다. bank.com 요청을 보낼 때마다 bank.com에서 설정한 쿠키가 전송되기 때문입니다. 은행은 전송받은 쿠키를 읽어 (해커가 아닌) 계정 주인이 접속한 것이라 생각하고 해커에게 돈을 송금합니다.

 

출처: https://ko.javascript.info/cookie

 

이런 공격을 크로스 사이트 요청 위조라고 부릅니다.

 

실제 은행은 당연히 이 공격을 막을 수 있도록 시스템을 설계합니다. bank.com에서 사용하는 모든 폼에 "XSRF 보호 토큰(protection token)"이라는 특수 필드를 넣어서 말이죠. 이 토큰은 악의적인 페이지에서 만들 수 없고, 원격 페이지에서도 훔쳐 올 수 없도록 구현되어 있습니다. 따라서 악의적인 페이지에서 폼을 전송하더라도 보호 토큰이 없거나 서버에 저장된 값과 일치하지 않기 때문에 요청이 무용지물이 됩니다.

 

하지만 이런 절차는 구현에 시간이 걸린다는 단점을 수반합니다. 모든 폼에 보호 토큰을 세팅해줘야 하죠. 또한 요청 전부를 검수해야 합니다.

 

 

🧷 samesite 옵션

쿠키의 samesite 옵션을 이용하면 “XSRF 보호 토큰” 없이도 (이론상으로) 크로스 사이트 요청 위조를 막을 수 있습니다.

이 옵션엔 두 가지 값을 설정할 수 있습니다.

 

samesite=strict(값을 설정하지 않고 그냥 samesite 옵션만 써줘도 동일하게 동작함)

 

사용자가 사이트 외부에서 요청을 보낼 때, samesite=strict 옵션이 있는 쿠키는 절대로 전송되지 않습니다.

메일에 있는 링크를 따라 접속하거나 evil.com과 같은 사이트에서 폼을 전송하는 경우 등과 같이 제3의 도메인에서 요청이 이뤄질 땐 쿠키가 전송되지 않죠.

인증 쿠키에 samesite 옵션이 있는 경우, XSRF 공격은 절대로 성공하지 못합니다. evil.com에서 전송하는 요청엔 쿠키가 없을 것이고, bank.com은 미인식 사용자에게 지급을 허용하지 않을 것이기 때문입니다.

이 보호장치는 꽤 믿을 만합니다. bank.com에서 수행하는 모든 작업은 samesite 쿠키를 함께 전송하기 때문이죠.

하지만 약간의 불편함도 감수해야 합니다.

만약 사용자가 메모장 등에 bank.com에 요청을 보낼 수 있는 링크를 기록해 놓았다가 이 링크를 클릭해 접속하면 bank.com 이 사용자를 인식하지 못하는 상황이 발생하기 때문입니다. 실제로 이런 경우 samesite=strict 옵션이 설정된 쿠키는 전송되지 않습니다.

이런 문제는 쿠키 두 개를 함께 사용해 해결할 수 있습니다. "Hello, John"과 같은 환영 메시지를 출력해주는 "일반 인증(general recognition)"용 쿠키, 데이터 교환 시 사용하는 samesite=strict 옵션이 있는 쿠키를 따로 둬서 말이죠. 이렇게 하면 외부 사이트를 통해 접근한 사용자도 정상적으로 환영 메시지를 볼 수 있습니다. 지급은 무조건 은행의 사이트를 통해서만 수행되도록 만들면 됩니다.

 

samesite=lax

 

samesite=lax는 사용자 경험을 해치지 않으면서 XSRF 공격을 막을 수 있는 느슨한 접근법입니다.

strict와 마찬가지로 lax도 사이트 외부에서 요청을 보낼 때 브라우저가 쿠키를 보내는 걸 막아줍니다. 하지만 예외사항이 존재합니다.

아래 두 조건을 동시에 만족할 때는 samesite=lax 옵션을 설정한 쿠키가 전송됩니다.

  1. “안전한” HTTP 메서드인 경우(예: GET 방식. POST 방식은 해당하지 않음).
    안전한 HTTP 메서드 목록은 RFC7231 명세에서 확인할 수 있습니다. 안전한 메서드는 읽기 작업만 수행하고 쓰기나 데이터 교환 작업은 수행하지 않습니다. 참고로, 링크를 따라가는 행위는 항상 GET 방식이기 때문에 안전한 메서드만 쓰입니다.
  2. 작업이 최상위 레벨 탐색에서 이루어질 때(브라우저 주소창에서 URL을 변경하는 경우).
    대다수의 작업은 이 조건을 충족합니다. 하지만 <iframe>안에서 탐색이 일어나는 경우는 최상위 레벨 탐색이 아니기 때문에 이 조건을 충족하지 못합니다. AJAX 요청 또한 탐색 행위가 아니므로 이 조건을 충족하지 못합니다.

브라우저를 이용해 자주 하는 작업인 "특정 URL로 이동하기"를 실행하는 경우, samesite=lax 옵션이 설정되어 있으면 쿠키가 서버로 전송됩니다. 노트에 저장된 링크를 여는 것도 특정 URL로 이동하는 행위이므로 위 조건들을 충족합니다.

하지만 외부 사이트에서 AJAX 요청을 보내거나 폼을 전송하는 등의 복잡한 작업을 시도할 때는 쿠키가 전송되지 않습니다.

이런 제약사항이 있어도 괜찮다면, samesite=lax 옵션은 사용자 경험을 해치지 않으면서 보안을 강화해 주는 방법으로 활용할 수 있을 것입니다.

samesite는 좋은 옵션이긴 하지만, 한가지 문제점이 있습니다.

 

오래된 브라우저(2017년 이전 버전)에선 samesite 옵션을 지원하지 않습니다.

 

따라서 samesite 옵션으로만 보안 처리를 하게 되면, 구식 브라우저에서 보안 문제가 발생할 수 있습니다.

구식 브라우저에 대응하지 못한다는 문제가 있긴 하지만, samesite 옵션을 XSRF 토큰 같은 다른 보안 기법과 함께 사용하면 보안을 강화할 있습니다. 구식 브라우저를 더는 사용하지 않는 때가 오면 XSRF 토큰 역시 필요하지 않겠죠.

 

 

ref: https://developer.mozilla.org/ko/docs/Web/API/Document/cookie

https://ko.javascript.info/cookie

Comments