[Node.js]-Cookie & Session

Updated:

Cookie 와 Session

=> 이 게시글은 로그인을 구현하기 위한 사전지식으로 Cookie와 Session에 대해 알아본다

-> 클라이언트가 보내는 요청에는 ‘누가 이 요청을 보내는지’에 관한 정보가 빠져있다. 이 ‘누가’를 알아내기 위해 로그인을 구현할 필요가 있고, 바로 로그인을 구현하기 위해서 Cookie와 Session이 필요하다
-> 이 ‘누구’를 식별하기 위해 서버가 먼저 쿠키를 응답과 함께 클라이언트에게 보낸다. 클라이언트는 이후부터 무언가 요청할때마다 쿠키를 헤더에 동봉해서 함께 보낸다. 즉, 서버에서 클라이언트로 쿠키를 보낼때만 코드를 작성해주면 된다. 여기서 쿠키는 ‘key-value’쌍의 형태를 지닌다

간단 예제


const http = require('http');

http.createServer((req, res)=>{
    console.log(req.url, req.headers.cookie);
    res.writeHead(200, {'Set-Cookie': 'myCookie=lim'});
    res.end('Send Cookie');
}).listen(8080, ()=>{
    console.log('서버가 8080포트에서 실행중~');
})

-> req.headers.cookie를 통해 쿠키를 가져올 수 있다
-> res.writeHead(200, {‘Set-Cookie’: ‘myCookie=lim’}) 와 같이 서버에서 클라이언트 측으로 쿠키를 보내면 된다

Cookie&Session1

-> 위와 같이 개발자 도구의 Network탭에서 Headers부분을 살펴보면 Set-Cookie값이 코드에서 작성한 것과 같이 설정되었음을 확인할 수 있다
-> 서버에서 클라이언츠 측으로 한번만 쿠키를 보내주면, 그 이후부터는 자동으로 클라이언트 측에서 서버로 쿠키를 보내주는데, 이를 Network탭의 Request Headers에서 확인할 수 있다

실전 예제


<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <title>Cookie & Session</title>
  </head>
  <body>
    <form action="/login">
      <input id="name" name="name" placeholder="이름을 입력하세요." />
      <button id="login">로그인</button>
    </form>
  </body>
</html>

const http = require('http');
const fs = require('fs').promises;
const path = require('path');

const parseCookies = (cookie = '')=>{			// 헤더에서 쿠키 찾아오는 함수
    return cookie
        .split(';')
        .map(e => e.split('='))
        .reduce((acc, [k,v])=>{
            acc[k.trim()] = decodeURIComponent(v);
            return acc;
        }, {});
};

http.createServer(async (req, res)=>{
    const cookies = parseCookies(req.headers.cookie);

    if(req.url.startsWith('/login')){			// url이 /login일 경우
        const url = new URL(req.url, 'http://localhost:8084');
        const name = url.searchParams.get('name');
        const expires = new Date();
        expires.setMinutes(expires.getMinutes() + 5);
        res.writeHead(302, {
            Location: '/',
            'Set-Cookie': `name=${encodeURIComponent(name)}; Expires=${expires.toGMTString()}; HttpOnly; Path=/`,
        });
        res.end();
    } else if(cookies.name){			// 쿠키가 이미 존재할 경우
        res.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});
        res.end(`${cookies.name}님 안녕하세요!`);
    } else{			// 쿠키가 존재하지도 않고 /login으로 접근하지도 않을 경우
        try{
            const data = await fs.readFile(path.join(__dirname, '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포트에서 실행중~');
});

-> ‘url이 /login일 경우’, ‘쿠키가 이미 존재할 경우’, ‘쿠키가 존재하지도 않고 url이 /login이 아닐경우’ 3가지로 나눠 처리하는 코드이다
-> 처음으로 ‘localhost:8084/’로 접근할 경우, ‘cookie.html’ 파일로 이동하며, 로그인페이지가 나타난다. 로그인페이지에서 로그인 버튼을 누르면, GET요청이므로 data는 /login뒤에 query string형식으로 들어간다
-> 이후 url.searchParams를 통해 query string을 가져와 get()으로 특정 파라미터를 추출한다
-> res.writeHead(302)는 리다이렉션을 의미하고, Location으로 리다이렉션할 주소를 적으면 된다
-> ‘Set-Cookie’ 의 속성 중 Expires를 설정하지 않을 경우, 브라우저가 종료되면 쿠키가 사라진다. 또한, HttpOnly 속성을 통해 JS에서 쿠키에 접근할 수 없도록 막는다

Session

-> Cookie 방식을 사용하면, 어느 쿠키를 가지고 있는지 브라우저의 개발자도구로 확인이 가능하므로, 누가 로그인을 했는지 모두 드러난다
-> 이를 해결하기 위해 서버에 사용자 정보를 저장하고, 클라이언트에게 세션아이디 같은 key값만 제공하는 방식을 Session이라고 한다

const http = require('http');
const fs = require('fs').promises;
const path = require('path');

const parseCookies = (cookie = '')=>{
    return cookie
        .split(';')
        .map(e => e.split('='))
        .reduce((acc, [k,v])=>{
            acc[k.trim()] = decodeURIComponent(v);
            return acc;
        }, {});
};

const session = {};

http.createServer(async (req, res)=>{
    const cookies = parseCookies(req.headers.cookie);
    if(req.url.startsWith('/login')){
        const url = new URL(req.url, 'http://localhost:8085');
        const name = url.searchParams.get('name');
        const expires = new Date();
        expires.setMinutes(expires.getMinutes() + 5);
        const uniqueInt = Date.now();
        console.log(uniqueInt);
        session[uniqueInt] = {
            name,
            expires,
        };
        res.writeHead(302, {
            Location: '/',
            'Set-Cookie': `session=${uniqueInt}; Expires=${expires.toGMTString()}; HttpOnly; Path=/`,
        });
        res.end();
    } else if(cookies.session && session[cookies.session].expires > new Date()){
        res.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});
        res.end(`${session[cookies.session].name}님 안녕하세요`);
    } else{
        try{
            const data = await fs.readFile(path.join(__dirname, '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(8085, ()=>{
    console.log('서버가 8085포트에서 실행중~');
});

-> 위의 Cookie 코드와 비슷한데, unique한 id값으로 구분되는 session이란 객체를 만들어서 그 안에 name과 expire정보를 담는다는 차이점이 존재한다
-> 마찬가지로, res.writeHead시에 302로 보내는데, 여기서 ‘Set-Cookie’의 session속성값으로 name이 아닌 id값을 보낸다는 차이가 있다
-> 해당 id값은 cookies.session으로 접근가능하고, session[cookies.session]으로 해당 id의 사용자 정보에 접근할 수 있다

Leave a comment