본문 바로가기
Code/JavaScript & Node.js

[Nodejs] 차근차근 Nodejs - 쿠키(cookie)

by 코드포휴먼 2020. 7. 21.

nodejs를 이용해 로그인 구현을 하려고 모듈을 알아보던 중, 쿠키와 세션에 대한 개념이 명확하지 않아 생활코딩 강의를 빠르게 정리한다.

먼저 쿠키에 대해 정리하도록 하겠다.

쿠키를 통해 수행되는 가장 중요한 일이 인증 기능이다.

본 강의는 인증이 무엇이고 인증을 어떻게 구현하는가에 대한 맥락을 파악하는 수업이다. 

실제로 적용하기엔 적합하지 않고 개념을 실습해보기 위한 내용이다.

강의 : https://opentutorials.org/course/3387

 

 

개인화란?

쿠키와 인증은 개인화와 관련이 있다.

개인화란 모든 사람에게 똑같은 웹페이지를 보여주는 것이 아니라 사람마다 선택과 취향에 맞는 웹페이지를 보여주는 것이다. 

예를 들어 로그인해서 장바구니에 물건을 담으면 다음에 로그인했을 때 그 물건을 다시 볼 수 있다. 

또는 한번 로그인해서 인증을 하면 그 이후엔(일정 기간 동안) 인증을 거치지 않아도 된다. 

이러한 것들이 개인화의 사례다. 

 

개인화에 대한 개념이 무르익던 1994년, 루먼 톨리가 쿠키를 도입시켰다. 

웹브라우저는 이전에 접속했던 사용자의 정보를 웹서버에 전송할 수 있게 됐다. 

따라서 웹서버는 이 정보를 바탕으로 현재 접속한 사용자가 누구인지 알 수 있게 됐다. 

 

 

쿠키 알아보기

쿠키 사용설명서를 살펴보자.
쿠키는 웹브라우저&웹서버가 주고받는 정보이기도 하면서 HTTP 프로토콜에 속해있는 기술이다.

 

설명서에 따르면 쿠키는 세 가지 용도가 있다. 

  1. 세션 관리(Session Management) - 서버에 저장해야할 로그인(인증), 장바구니, 게임 스코어 등의 정보 관리 
  2. 개인화(Personalization) - 사용자 선호, 테마 등의 세팅
  3. 트랙킹(Tracking) - 사용자 행동을 기록하고 분석하는 용도

 

그럼 쿠키는 어떻게 만드나.
Set-Cookie라는 이름의 헤더값을 응답하는 HTTP 메세지에 적혀진 대로 셋팅하면 된다.

mozilla의 <HTTP 쿠키> 캡처 화면

 

예를 들면 아래와 같다.

맨 위에 HTTP의 응답 메시지가 있다.
그리고 Set-Cookie 항목에 yummy_cookie=choco 쿠키에 값을 적용하는 응답 메시지를 작성한다. 

mozilla의 <HTTP 쿠키> 캡처 화면

 

쿠키 만들어보기 (CRUD의 C)

먼저 생활코딩의 web nodejs 기본 베이스 코드를 다운 받아 npm install 하고 실행한다.   

나의 경우엔 이 베이스 코드를 이용해서 다른 기능도 실습할 예정이므로 cookie라는 깃 브랜치를 만들어서 실습했다.

nodejs를 이용해서 쿠키를 만들어보겠다. 

 

우선 nodejs 폴더에 cookie.js를 추가해서 아주 간단한 웹서버를 만든다.

var http = require('http');

http.createServer(function(request, response){
    response.end('Cookie!');
}).listen(3000);

 

서버를 실행시킨다.

 

개발자도구에서 Network를 클릭하면 웹브라우저와 웹서버가 통신하는 내용을 관찰할 수 있다.

localhost라는 서버로 접속할 때 통신 내용을 볼 수 있는 부분을 클릭한다. 
Headers 안에 Response Headers에 쿠키에 대한 내용이 없다. 아무것도 안 했기 때문이다.

 

아까 우리가 봤던 Set-Cookie 값을 전송해보겠다. 
응답하는 메세지를 조작하면 된다.

 

response.writeHead 메소드는 첫 번째 인자로 상태코드를, 두 번째 인자로 객체를 받는다.

그 후 쿠키를 key, value 형식으로 넣으면 된다.

예시에선 Set-Cookie가 복수 형태로 주어지는데, 그 경우엔 인자로 배열을 주는 것으로 약속돼있다. 

var http = require('http');

http.createServer(function(request, response){
    // response.writeHead(상태코드, 객체)
    response.writeHead(200, {
       'Set-Cookie':['yummy_cookie=choco', 'tasty_cookie=strawberry']
    });
    response.end('Cookie!');
}).listen(3000);

 

Set-Cookie라는 두 개의 헤더값이 추가됐다. 

현재로썬 Response Header에만 있고 Request Header엔 없다. 

 

이 부분을 주석처리해서 서버가 웹브라우저에게 Set-Cookie 헤더를 보내지 않도록 해본다.

var http = require('http');

http.createServer(function(request, response){
    // response.writeHead(상태코드, 객체)
    // response.writeHead(200, {
    //    'Set-Cookie':['yummy_cookie=choco', 'tasty_cookie=strawberry']
    // });
    response.end('Cookie!');
}).listen(3000);

 

웹브라우저가 요청을 보낼 때 Request Headers에 Cookie값이 심어져 있다. Response에는 주석처리 했으니 더이상 쿠키를 보내지 않는다. 

이제부터 웹브라우저는 reload를 할 때마다 Set-Cookie에 의해 구워진(저장된) 쿠키값을 Cookie라는 헤더값을 통해 서버로 전송하고 있는 것이다.

 

어떤 쿠키가 현재 웹브라우저에 주어져있는가를 보고 싶으면 서버의 Cookies 탭을 확인하면 된다.

현재는 두 개의 쿠키가 만들어졌고, Request 쿠키로서 전송되고 있다.

(Applications 탭에 가면 특정 서버의 쿠키를 지울 수가 있다. -> 🚫 Clear All 버튼 클릭)

 

 

쿠키 확인하기 (CRUD의 R)

웹브라우저가 웹서버로 전송한 쿠키를 어떻게 읽을 수 있을까?

생성한 쿠키를 다시 웹브라우저가 서버로 전송했을 때 웹 애플리케이션 안에선 어떻게 알아낼까? 

 

스택오버플로우 QnA에서 힌트를 얻을 수 있었다.

우선 createServer 메소드에서 request(요청 정보를 가진 객체)를 parseCookies라는 함수에 전달한다. 

그리고 parseCookies 함수 안에서는 request.headers.cookie 즉 request 객체의 헤더의 쿠키에 접근한다. 

본 코드를 바탕으로 쿠키를 받았는지 확인하는 코드를 작성해보겠다. 

stackoverflow의 <Get and Set a Single Cookie with Node.js HTTP Server> 캡처 화면 

 

먼저 간단하게 request.headers.cookie를 콘솔에 찍어보겠다. 

이전에 작성한 코드에서 붙였던 주석을 지우고 실행해서 쿠키를 다시 만든다. 

그러면 Request Headers에서 쿠키를 전송한 것을 볼 수 있다. 

이 쿠키를 웹서버는 Request Headers 값으로 받았을 테니 쿠키가 콘솔에 찍힐 것이다. 

var http = require('http');

http.createServer(function(request, response){
    console.log(request.headers.cookie)
    
    response.writeHead(200, {
      'Set-Cookie':['yummy_cookie=choco', 'tasty_cookie=strawberry']
    });
    response.end('Cookie!');
}).listen(3000);

 

먼저 개발자도구에선 다음과 같이 Request, Response Cookies에 해당 쿠키가 확인된다. 

 

그리고 콘솔에도 쿠키가 문자열 형태로 찍힌다.

쿠키가 복수개이면 지저분하게 문자로 퉁쳐서 준다.

 

스택오버플로우에서 봤던 코드는 이 형태를 분석해서 객체로 반환하는 코드였다.

그 코드를 그대로 사용해도 되지만 npm에는 쿠키를 핸들링할 수 있는 cookie 모듈이 존재한다.

따라서 cookie 모듈을 설치한다. 

npm install -s cookie

 

코드에서는 아래처럼 불러오면 사용할 수 있게 된다. 

var cookie = require('cookie');

 

cookie는 parse라는 메소드를 가지고 있다.

nodejs가 출력했던 문자열 형태의 쿠키를 cookie 모듈의 parse 메소드에 인자로 전달하면 객체화된 쿠키를 반환한다. 

npm <cookie 모듈> 캡쳐 화면

 

분석한 결과가 cookies 변수에 담겨있으니 콘솔에 cookies를 출력하면 중괄호로 둘러싸인 객체를 볼 수 있다. 

var http = require('http');
var cookie = require('cookie');

http.createServer(function(request, response){
    console.log(request.headers.cookie)   

    var cookies = cookie.parse(request.headers.cookie);
    console.log(cookies);

    response.writeHead(200, {
      'Set-Cookie':['yummy_cookie=choco', 'tasty_cookie=strawberry']
    });
    response.end('Cookie!');
}).listen(3000);

 

서버를 재실행하면 두 개의 출력문을 볼 수 있다. 

아래 것이 cookie 모듈의 parse 함수에 의해 만들어진 객체 형태의 쿠키다.

 

객체이므로 yummy_cookie에 접근하고 싶다면 cookies.yummy_cookie 와 같이 접근하면 된다. 

var http = require('http');
var cookie = require('cookie');

http.createServer(function(request, response){
    console.log(request.headers.cookie)  

    var cookies = cookie.parse(request.headers.cookie);
    console.log(cookies);
    console.log(cookies.yummy_cookie);

    response.writeHead(200, {
      'Set-Cookie':['yummy_cookie=choco', 'tasty_cookie=strawberry']
    });
    response.end('Cookie!');
}).listen(3000);

 

콘솔에서 choco가 반환된 것을 볼 수 있다.

이렇게 웹서버에서 전달받은 쿠키를 확인할 수 있다.

 

그러나 작성했던 코드는 쿠키값이 없을 때 문제가 발생한다.

웹브라우저 도구에서 쿠키값을 지워버리면 리퀘스트에서 넘긴 request.headers.cookie 값이 undefined가 되어버린다. 

cookie 모듈의 parse는 undefined를 수용할 수 없는 함수이므로 오류가 나고 서버가 종료된다. 

따라서 조건을 걸어서 쿠키의 값을 세팅한다. 조건 걸기 전에 cookies 변에 빈 객체를 만든다. 

var http = require('http');
var cookie = require('cookie');

http.createServer(function(request, response){
    console.log(request.headers.cookie)  

    var cookies = {};
    if (request.headers.cookie !== undefined){
        var cookies = cookie.parse(request.headers.cookie);
    }
    console.log(cookies);
    console.log(cookies.yummy_cookie);

    response.writeHead(200, {
      'Set-Cookie':['yummy_cookie=choco', 'tasty_cookie=strawberry']
    });
    response.end('Cookie!');
}).listen(3000);

 

웹 브라우저에서 쿠키를 지우고 실행하면 쿠키를 받지 않아도 조건문에 의해 서버가 중단되지 않는다. 

 

 

쿠키 사용하기

쿠키는 어떻게 사용될까?

쿠키값을 통해 웹사이트가 사용자에 맞는 웹사이트 설정을 적용하여 보낼 수 있다. 

예를 들어 웹사이트 설정(ex.웹사이트 언어)을 원하는 대로 바꾸면 개인화된 정보를 커스터마이징한 웹페이지(ex.한국어로 설정된 웹사이트)를 전달해준다.

 

특정 사이트에 접속해서 확인해본다. 

아래의 경우 쿠키에서 lang 이름에 해당하는 ko라는 값을 볼 수 있다. 

 

lang의 값을 en으로 바꾸어서 웹사이트 도메인 pixabay.com에 다시 접근하면 영문 웹페이지가 나온다.

쿠키의 값이 반영되어 사용자에 맞는 정보대로 보여준다.

 

로그인을 하면 쿠키에 sessionid 라는 항목이 추가된다. 로그인한 사용자에 대한 식별자다. 

이 식별자는 비밀번호 같은 어떠한 정보도 담고 있으면 안 된다. 
식별자가 노출되면 개인정보가 털릴 수 있기 때문이다. 

 

sessonid의 값을 가지고 사용자의 비밀번호 등을 알아낼 순 없다.
다만 이 값을 가지고 나 대신에 로그인을 할 수 있다. 

현재 크롬 브라우저에서 실행중인데 웨일 브라우저에서 sessionid 쿠키항목을 추가해 값을 넣어주겠다.

넣어서 페이지를 reload하면 회원마크가 뜨면서 로그인이 된다.

 

sessionid는 로그인에 성공한 사용자에 대한 식별자다. 

아이디와 비밀번호 같은 정보는 없지만 sessionid 값이 있다면 로그인은 될 수 있다. 

이 값이 가로채진다면 나 대신 로그인할 수 있는 보안사고가 발생한다.  

쿠키는 굉장히 중요한 정보로 사용된다는 것을 명심하자.

 

 

쿠키 제어하기

쿠키가 언제까지 살아있게 할 것인가를 제어할 수 있다. 
쿠키의 종류는 두 가지로 나뉜다.

  1. Session cookies : 웹브라우저 실행 시에만 유효한 쿠키 (웹브라우저 종료 시 소멸) 
  2. Permanent cookies : 계속적으로 유효한 쿠키 (웹브라우저 종료해도 소멸하지 않음)

앞서 코드에서 지정한 방식의 쿠키는 Session cookies에 해당된다.

쿠키 사용설명서에 따르면 Expires 또는 Max-Age와 같은 설정을 추가하면 Permanent Cookies가 된다.

  • Expires : 쿠키가 언제 만료될지 절대적인 date 값을 부여받는다.
  • Max-Age : 현재 시점을 기준으로 쿠키가 얼마 동안 유지될 것인지 초 단위 숫자를 부여받는다. 

아래에서 30일 기간에 해당하는 Permanent한 쿠키를 새로 만들었다.

작은 따옴표가 아닌 grabe accent임에 주의한다.

(쿠키 이름 Permanent와 값 cookies엔 아무 의미가 없으니 지정한 옵션 값 Max-Age에만 집중한다.)

var http = require('http');
var cookie = require('cookie');

http.createServer(function(request, response){
    console.log(request.headers.cookie)   

    var cookies = {};
    if (request.headers.cookie !== undefined){
        var cookies = cookie.parse(request.headers.cookie);
    }
    console.log(cookies);
    console.log(cookies.yummy_cookie);

    response.writeHead(200, {
      'Set-Cookie':[
          'yummy_cookie=choco', 
          'tasty_cookie=strawberry',
          `Permanent=cookies; Max-Age=${60*60*24*30}`
        ]
    });
    response.end('Cookie!');
}).listen(3000);

 

서버를 켜서 확인해본다.

기존 쿠키는 만료 날짜가 세션이지만 Permanent 쿠키는 한달 뒤의 날짜가 지정되어 있다.

 


또한 쿠키에 보안 설정을 해서 제어할 수 있다.

쿠키 사용설명서에 따르면 Secure 옵션값을 추가할 수 있다.

웹브라우저와 웹서버가 HTTPS 통신을 하는 경우에만 쿠키를 전송한다는 설정이다. 

 

Secure라는 쿠키에 Secure을 지정해서 만들어보겠다. 

(쿠키 이름 Secure와 값 cookies엔 아무 의미가 없으니 지정한 옵션 값 Secure에만 집중한다.)

var http = require('http');
var cookie = require('cookie');

http.createServer(function(request, response){
    console.log(request.headers.cookie)   

    var cookies = {};
    if (request.headers.cookie !== undefined){
        var cookies = cookie.parse(request.headers.cookie);
    }
    console.log(cookies);
    console.log(cookies.yummy_cookie);

    response.writeHead(200, {
      'Set-Cookie':[
          'yummy_cookie=choco', 
          'tasty_cookie=strawberry',
          `Permanent=cookies; Max-Age=${60*60*24*30}`,
          'Secure=cookies; Secure'
        ]
    });
    response.end('Cookie!');
}).listen(3000);

 

서버를 다시 켜서 쿠키를 확인하면 아래와 같다.

Response Cookies에는 Secure 이름의 쿠키가 있지만 Request Cookies에는 없다.

현재 통신이 HTTP로 되고 있기 때문이다. 

 

https로 요청한다면 웹브라우저가 웹서버에게 쿠키를 전송할 것이다.

(현재 환경에선 https 요청 설정을 안 했기 때문에 실습은 불가)

이 설정이 필요한 이유는 누군가 cookie를 가로채지 않도록 방지하기 위해서다.

HTTP가 아닌 HTTPS 통신을 통해 cookie를 보호할 수 있다.

 

 

한편 HttpOnly 옵션 설정이 있다. 
웹브라우저와 웹서버가 통신할 때만 쿠키를 발행한다는 설정이다. 

HttpOnly 설정을 부여한 쿠키를 만들어보겠다.

(쿠키 이름 HttpOnly와 값 cookies엔 아무 의미가 없으니 지정한 옵션 값 HttpOnly에만 집중한다.)

var http = require('http');
var cookie = require('cookie');

http.createServer(function(request, response){
    console.log(request.headers.cookie) 

    var cookies = {};
    if (request.headers.cookie !== undefined){
        var cookies = cookie.parse(request.headers.cookie);
    }
    console.log(cookies);
    console.log(cookies.yummy_cookie);

    response.writeHead(200, {
      'Set-Cookie':[
          'yummy_cookie=choco', 
          'tasty_cookie=strawberry',
          `Permanent=cookies; Max-Age=${60*60*24*30}`,
          'Secure=cookies; Secure',
          'HttpOnly=cookies; HttpOnly'
        ]
    });
    response.end('Cookie!');
}).listen(3000);

 

서버를 실행하고 새로고침을 통해 페이지를 reload하면 Response Cookies에 담겼던 HttpOnly 이름의 쿠키가 Request Cookies에도 담긴다.

개발자 도구에서 esc를 누르면 Console 창이 뜨는데 이곳에서 javascript 코드로 웹브라우저를 직접 제어할 수 있다.

document.cookie를 통해 javascript로 존재하는 쿠키값을 가져올 수 있다.

그러나 HttpOnly 옵션 플래그값이 적용된 쿠키는 javascript가 인지할 수 없다. 

javascript를 이용해서 쿠키를 훔치는 일이 많기 때문에 HttpOnly 옵션을 부여해서 쿠키를 보호하는 것이다.

 

 

이번엔 Path 옵션을 사용해보겠다.

특정 디렉토리에서만 쿠키가 활성화되도록 하는 설정이다.

(쿠키 이름 Path와 값 cookies엔 아무 의미가 없으니 지정한 옵션 값 Path에만 집중한다.)

var http = require('http');
var cookie = require('cookie');

http.createServer(function(request, response){
    console.log(request.headers.cookie) 

    var cookies = {};
    if (request.headers.cookie !== undefined){
        var cookies = cookie.parse(request.headers.cookie);
    }
    console.log(cookies);
    console.log(cookies.yummy_cookie);

    response.writeHead(200, {
      'Set-Cookie':[
          'yummy_cookie=choco', 
          'tasty_cookie=strawberry',
          `Permanent=cookies; Max-Age=${60*60*24*30}`,
          'Secure=cookies; Secure',
          'HttpOnly=cookies; HttpOnly',
          'Path=cookies; Path=/cookie'
        ]
    });
    response.end('Cookie!');
}).listen(3000);

 

쿠키 설정 부분('Set-Cookie' 키와 배열 값)을 주석처리하고 페이지를 리로드해도 쿠키는 살아있을 것이다.

그러나 루트 디렉토리(localhost:3000)에서 리로드하면 Path 쿠키는 없을 것이다.

localhost:3000/cookie 디렉토리에 접속하면 경로를 설정했던 Path 쿠키를 확인할 수 있다.

localhost:3000/cookie/sub 등의 하위 디렉토리에서 접속해도 Path 쿠키는 살아있다.

Path 옵션을 설정한 디렉토리와 해당 하위디렉토리에만 해당하는 쿠키만을 웹서버에 전달한다.

 

 

 

여유로울 때 추가자료를 통해 쿠키를 이용해서 개인화된 애플리케이션 구현을 연습해봐야겠다.

(추가자료는 사용자의 선택-night/day을 쿠키에 저장해서 다음에 방문했을 때 같은 화면을 볼 수 있도록 처리한 웹이다.)

 

쿠키의 등장을 통해 통신했던 내용을 기억할 수 있게 되었다.

과거에는 쿠키가 브라우저의 정보를 저장하는 유일한 방법이었지만 오늘날엔 localStorage, Indexed DB 등으로 가능하다고 한다. 

새로운 대안은 더 편리하거나 더 많은 데이터를 저장할 수 있다. 쿠키는 4KB 이상의 데이터를 저장할 수 없다. 

 

한편 HTTPS 통신을 하지 않는다면 웹브라우저와 웹서버 사이에서 쿠키를 낚아챌 가능성이 다분하다. 

인증을 구현한다면 비밀번호 같은 인증 정보 자체가 쿠키에 담기는 쿠키 인증보다는 세션 인증 방식이 더 안전하다.

 


출처 

https://opentutorials.org/course/3387

github.com/web-n/Nodejs

https://developer.mozilla.org/ko/docs/Web/HTTP/Cookies

stackoverflow.com/questions/3393854/get-and-set-a-single-cookie-with-node-js-http-server

https://www.npmjs.com/package/cookie

댓글