3-5. 노드 쿠키의 모든 것
I. 쿠키의 필요성
1. 요청에는 한 가지 단점이 있음
- 누가 요청을 보냈는지 모름(IP 주소와 브라우저 정보 정도만 알 수 있음)
- 로그인을 구현하면 되지만, 그러려면 쿠키와 세션이 필요
2. 쿠키 : 키=값의 쌍
- name=junu
- 매 요청마다 서버에 동봉해서 보냄
- 서버는 쿠키를 읽어 누구인지 파악
II. 쿠키 서버 만들기
1. 쿠키 넣는 것을 직접 구현
- writeHead : 요청 헤더에 입력하는 메서드
- Set-Cookie : 브라우저에게 쿠키를 설정하라고 명령
2. 쿠키 : 키=값의 쌍
- name=junu
- 매 요청마다 서버에 동봉해서 보냄
// cookie.js
const http = require('http');
http.createServer((req, res) => {
console.log(req.url, req.headers.cookie);
res.writeHead(200, { 'Set-Cookie': 'mycookie=test' });
res.end('Hello Cookie');
})
.listen(8083, () => {
console.log('8083번 포트에서 서버 대기 중입니다!');
});
위 코드를 실행시키고 나서 8083 포트에 접속한다.
그 다음 개발자도구(F12)를 열고, Network(네트워크) 탭에서 locathost > Headers 에 있는 Response Headers 를 살펴보면
아래와 같이 Set-Cookie: mycookie-test 를 확인할 수 있다.
또한 아래와 같이, Application(응용 프로그램) 탭에 있는 Cookies > http://localhost:8083 을 살펴보면 쿠키 정보를 확인할 수 있다.
쿠키의 유효기간(Expires)을 따로 설정하지 않는다면 Session 이 되는데, 이는 브라우저가 닫히기 전까지 유효하다는 뜻이다.
그리고 콘솔창을 확인해보면 아래와 같이 정보가 나와있는 것을 볼 수 있다.
이 때, / 요청과 /favicon.ico 요청이 있다.
favicon.ico 는 크롬에서 이 이미지를 알아서 요청하기 때문에 발생하는 요청으로, 그냥 무시하면 된다.
III. 헤더와 본문
http 요청과 응답은 헤더와 본문을 가짐
- 헤더는 요청 또는 응답에 대한 정보를 가짐
- 본문은 주고 받는 실제 데이터를 가짐
- 쿠키는 부가적인 정보이므로 헤더에 저장
IV. 쿠키로 나를 식별하기
쿠키에 내 정보를 입력
- parseCookies : 쿠키 문자열을 객체로 변환
- 주소가 /login 인 경우와 / 인 경우로 나뉨
- /login 인 경우 쿼리스트링으로 온 이름을 쿠키로 저장
- 그 외의 경우 쿠키가 있는지 없는지 판단
// cookie2.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>쿠키&세션 이해하기</title>
</head>
<body>
<form action="/login">
<input id="name" name="name" placeholder="이름을 입력하세요" />
<button id="login">로그인</button>
</form>
</body>
</html>
// cookie2.js
const http = require('http');
const fs = require('fs').promises;
const url = require('url');
const qs = require('querystring');
const parseCookies = (cookie = '') =>
cookie
.split(';')
.map(v => v.split('='))
.reduce((acc, [k, v]) => {
acc[k.trim()] = decodeURIComponent(v);
return acc;
}, {});
http.createServer(async (req, res) => {
const cookies = parseCookies(req.headers.cookie); // { mycookie: 'test' }
// 주소가 /login으로 시작하는 경우
if (req.url.startsWith('/login')) {
const { query } = url.parse(req.url);
const { name } = qs.parse(query);
const expires = new Date();
// 쿠키 유효 시간을 현재시간 + 5분으로 설정
expires.setMinutes(expires.getMinutes() + 5);
res.writeHead(302, {
Location: '/',
'Set-Cookie': `name=${encodeURIComponent(name)}; Expires=${expires.toGMTString()}; HttpOnly; Path=/`,
});
res.end();
// name이라는 쿠키가 있는 경우
} else if (cookies.name) {
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end(`${cookies.name}님 안녕하세요`);
} else {
try {
const data = await fs.readFile('./cookie2.html');
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(data);
} catch (err) {
res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end(err.message);
}
}
})
.listen(8084, () => {
console.log('8084번 포트에서 서버 대기 중입니다!');
});
✨ cookie2.js 파일에서 /login 요청일 때, res.writeHead 를 주의깊게 살펴보자
① name=${encodeURIComponent(name)} : 이름 데이터를 인코딩해서 받아라.
- cookie 에서는 한글을 사용하면 보안적으로 문제가 되기 때문에, 한글을 입력할 수 없게끔 되어있다.
따라서, 한글 데이터를 전달받으려면 encodeURIComponent 로 인코딩하여 받아야한다.
(이거 안쓰면 쿠키에 이상한 글자가 들어갔다는 에러 발생)
② Expires=${expires.toGMTString()} : 쿠키의 만료기간(유효기간) 설정
- 위에서 말했듯이, 쿠키의 만료기간을 설정하지 않으면 Session 이 되고, 이는 브라우저 종료 시에 쿠키가 만료된다는 것을 의미한다.
반드시 필요한 설정은 아니지만 필요하다면, 해당 코드 3줄 상단에 있는 expires.setMinutes(expires.getMinutes() + 5) 처럼,
세션의 만료기간을 '현재시간으로부터 5분 후' 로 설정할 수 있다.
③ HttpOnly : JavaScript로 쿠키에 접근할 수 없게끔 한다.
- 보통 쿠키들은 JavaScript로 접근하게 되면 보안에 위협을 가하기 때문에, 특히 로그인을 위해 사용하는 쿠키에는 거의 필수적인 요소이다.
④Path=/ : / 아래에 있는 주소에서는 쿠키가 모두 유효하다.
위의 코드들을 잘 읽어보고 난 다음에 분석에 성공하셨다면, 직접 실습해보면서 따라하는 것을 추천한다.
그리고 ✨ 부분을 집중해서 공부하고 나면, 5분이라는 시간은 거뜬히 지났을 것이다.
그렇다면, 8084 페이지에서 새로고침을 눌러보자.
로그인 되어있던 것이 풀리지 않았습니까?
과연 왜 그런 것일까?
여러분들은 이미 정답을 알고 있다.
이번 게시글을 잘 읽어보면서 정답을 스스로 찾아보자.