쿠키와 세션 이해하기

올챙이·2024년 8월 24일

Node.js

목록 보기
10/23
post-thumbnail

클라이언트 요청

클라이언트에서 보내는 요청에는 단점이 존재합니다.
누가 요청을 보내는지 모른다는 것입니다.
그렇기 때문에 로그인을 구현해야합니다.
로그인을 구현하기 위해 쿠키세션에 대해서 알아야 합니다.

쿠키

쿠키란?

쿠키는 웹 브라우저가 서버와의 통신에서 사용하는 작은 데이터 조각입니다. 사용자의 로그인 정보나 세션 정보 등을 저장하는 데 사용됩니다.

쿠키 흐름

  1. 요청을 보낸 사람이 누구인지 기억하기 위해 서버는 요청에 대한 응답을 할 때 쿠키하는 것을 같이 보냅니다.

  2. 서버로부터 쿠키가 오면, 웹 브라우저는 쿠키를 저장해뒀다가 다음에 요청할 때마다 쿠키를 동봉해서 보냅니다.

  3. 서버는 요청에 들어 있는 쿠키를 읽어서 사용자가 누구인지 파악합니다.

  4. 브라우저는 쿠키가 있다면 자동으로 동봉해서 보내주므로 따로 처리할 필요가 없습니다.

쿠키 만들기

1
2
3
4
5
6
7
8
9
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번 포트에서 서버 대기 중입니다!");
});
cs
  • 응답

    • 첫번째의 req.headers.cookie가 undefined 상태입니다.
    • 두번째의 req.headers.cookie에 쿠키가 정상적으로 심어졌습니다.
    • 두번째 요청
      • favicon.ico : 웹 사이트 탭 왼쪽에 보이는 이미지 입니다.
      • 요청이 2번인 이유 : 브라우저는 파비콘이 뭔지 HTML에서 유추할 수 없으면 서버에 추가 요청을 보냅니다.

사용자 식별

  • cookie2.html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!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>
    cs
  • cookie2.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    const http = require("http");
    const fs = require("fs").promises;
    const path = require("path");
     
    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 url = new URL(req.url, "http://localhost:8084");
            const name = url.searchParams.get("name");
            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(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번 포트에서 서버 대기 중입니다!");
    });
    cs
    • parseCookies 함수

      • 쿠키 문자열을 쉽게 사용하기 위해 자바스크립트 객체 형식으로 바꾸는 함수
    • req.headers.cookie

    • url.searchParams.get("name")
      요청된 url에서 name이라는 특정 쿼리 파라미터 값을 추출합니다.

  • Location
    리다이렉트 주소입니다.

  • name=${encodeURIComponent(name)}
    name의 정보를 쿠키에 넣어줍니다.

  • Expires=${expires.toGMTString()}
    쿠키 유효 시간입니다.

  • HttpOnly
    설정 시 자바스크립트에서 쿠키에 접근할 수 없습니다. 쿠키 조작을 방지하기 위해 설정하는 것이 좋습니다.

  • Path=URL
    쿠키가 전송될 URL을 특정할 수 있습니다.

세션 사용

서버에 사용자 정보를 저장하고 클라이언트와는 세션 아이디로만 소통합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
const http = require("http");
const fs = require("fs").promises;
const path = require("path");
 
const parseCookies = (cookie = ""=>
    cookie
        .split(";")
        .map((v) => v.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();
        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번 포트에서 서버 대기 중입니다!");
});
cs
  • const session = {};
    사용자 세션을 관리하는 객를 생성합니다.

  • const uniqueInt = Date.now();
    현재 시간을 밀리초로 변환한 값을 세션 ID로 사용합니다.

  • session[uniqueInt]
    사용자 이름과 세션 만료 시간을 저장합니다.

0개의 댓글