쿠키 ( Cookie )
포스트
취소

쿠키 ( Cookie )

해당 포스트는 Cookie에 대해 정리한 포스트입니다.

🍪 쿠키

클라이언트인 브라우저에 저장되는 작은 데이터 조각이 쿠키의 본체입니다.
쿠키의 값은 반드시 인코딩해야하고, 최대 크기는 4KB, 최대 개수는 20여 개 정도로 한정된다고 합니다.

쿠키를 주고 받을 때는 따로 설정해주지 않아도 서버에서 cookie를 보내면 브라우저에 등록되며, cookie를 보낸 서버로 응답을 하면 쿠키를 실어서 보냅니다.
단, 서로 다른 도메인에게 쿠키를 전달할 때는 credentials를 허용해줘야 합니다.

서버에서 브라우저로 쿠키를 전달하는 방법은 사용하는 프레임워크/라이브러리마다 편하게 사용할 수 있는 방법을 제공합니다.
하지만 근본적으로는 Set-Cookie라는 헤더에 : key=value; option1=v; maxAge=100000; SameSite=None 같이 주고 받습니다.

브라우저에서 Set-Cookie를 보게 되면 자체적으로 쿠키를 등록합니다.
또한 서버로 응답할 때도 쿠키의 옵션에 따라서 다르겠지만, 일반적으로 서버에 쿠키를 전달해줍니다.
따라서 서버에서 쿠키를 주기만 한다면 브라우저에서 자체적으로 송/수신 처리를 해주고 유효기간이 지나면 알아서 제거해줍니다.

쿠키 주고 받기 쿠키 주고 받기 ( 네트워크 탭 ) 쿠키 주고 받기 쿠키 주고 받기 ( 네트워크 탭 )

0️⃣ 목적

테마, 광고, 인증 등의 목적으로 사용될 수 있습니다.
주로 서버에 의해 제작되며 클라어언트와 서버간의 인증 목적으로 많이 사용됩니다.

HTTP 기준으로 서버는 상태를 유지하지 않기 때문에 클라이언트를 구분할 수 없습니다.
따라서 클라이언트를 구분할 수 있는 식별자 데이터를 쿠키에 넣어두고 서버로 요청할 때마다 쿠키를 동봉해서 같이 보냅니다.
그러면 서버에서는 쿠키를 보고 어떤 사용자인지 판단할 수 있습니다.

일반적으로 Cookie + Session 혹은 Cookie + Token의 조합을 사용해서 위와 같은 로직을 구현합니다.

쿠키는 브라우저에 저장되는 정보이기 때문에 보안적인 위험이 있기 때문에 중요한 정보를 저장하면 안됩니다.
대부분 쿠키에 바로 의미있는 값을 저장하기 보다는 암호화돼서 서버의 비밀 키를 통해서만 검증할 수 있는 값을 넣어서 줍니다.
그렇게 되면 쿠키가 노출되어도 서버의 비밀 키가 없다면 아무런 의미가 없는 값이기 때문에 어느정도 안전하게 됩니다.

추가적으로 쿠키는 요청과 응답에 알아서 동봉되기 때문에 너무 많은 데이터를 담으면 네트워크 비용 측면에서 낭비가 발생하기 때문에 가능한 가볍게 만드는 것이 좋습니다.

1️⃣ 옵션

  1. path: 명시된 경로나 하위 경로에서만 cookie에 접근할 수 있음 ( 일반적으로 / )
  2. domain: cookie를 접근할 수 있는 도메인 결정 ( 일반적으로 생성한 도메인 ) ( 값을 입력하면 서브 도메인에서도 접근 가능 )
  3. expires: 유효 일자 ( date.toUTCString()으로 세팅 )
  4. max-age: 만료 기간 ( ms )
  5. secure: HTTPS인 경우에만 cookie가 전송됨 ( localhost 제외 )
  6. samesite: CSRF 공격을 막기 위한 설정
    • none: domain이 일치하지 않더라도 쿠키 전달 ( secure이 반드시 true여야 함 )
    • lax: domain만 일치하면 쿠키 전달
    • strict: 쿠키를 생성한 domain이 정확하게 일치해야 쿠키 전달 ( 기본 값 )
  7. httponly: JavaScriptcookie를 읽지 못하게 만듦

만약 지속 시간을 입력하지 않으면 브라우저가 닫힐 때 제거됩니다. ( 세션 쿠키라고 부름 )
( 지속 시간은 expiresmax-age를 입력하지 않았을 때를 의미합니다. )
( expiresmax-age를 둘 다 입력하면 expires로 적용됩니다. )

세션 쿠키는 아래에서 설명할 세션과는 연관이 없는 그냥 브라우저가 살아있는 동안에만 존재하는 쿠키를 의미합니다.

쿠키는 세션 혹은 토큰을 전달과 식별하는 목적으로 주로 사용됩니다.

📜 사용 예시

0️⃣ 브라우저에서 쿠키 생성

;으로 구분하고 옵션 값들을 넣을 수 있습니다.

1
document.cookie = "x=10; path=/; max-age=10;"

1️⃣ Express.js를 이용한 쿠키

실질적인 사용 예시보다는 어떻게 쿠키를 생성하고 송/수신하는지에 대해서만 작성했습니다.
( 프론트는 http://localhost:5500, 서버는 http://localhost:3000이라고 가정 )

1. 프론트 코드

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
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>

  <script defer src="./index.js"></script>
</head>
<body>
  <button type="button">click me</button>
  
  <script>
    const $button = document.querySelector("button");

    $button.addEventListener("click", () => {
      fetch("http://localhost:3000", {
        method: "POST",
        // 서버로 응답할 때 쿠키를 같이 보내기 위한 설정
        credentials: "include",
        headers: {
          "Content-Type": "application/json",
        },
      })
        .then((res) => res.json())
        .then(console.log);
    });
  </script>
</body>
</html>

2. 서버 코드 ( Express )

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
50
51
52
53
54
55
56
57
const express = require("express");
const cors = require("cors");
const morgan = require("morgan");
const cookieParser = require("cookie-parser");

const app = express();

app.use(morgan("dev"));
app.use(cookieParser());
// // (3) 서명된 쿠키 파싱
// app.use(cookieParser("keyboard cat"));

app.options("/", cors({ origin: "http://localhost:5500", credentials: true }));

app.post("/", (req, res) => {
  // (1)
  res.header("Access-Control-Allow-Credentials", true);
  // (1)
  res.header("Access-Control-Allow-Origin", "http://localhost:5500");

  // // (2)
  // res.header("Set-Cookie", "my-cookie=keyboard; path=/; max-age=10;");
  res.cookie("my-cookie", "keyboard", {
    httpOnly: true,
    secure: true,
    sameSite: "none",
    domain: "localhost",
    path: "/",
    maxAge: 1000,
    expires: new Date().toUTCString(),
    // (4) 쿠키 서명
    // signed: true,
  });
  
  console.log("req.headers.cookie >> ", req.headers.cookie);
  console.log("req.cookies >> ", req.cookies);
  // (5)
  console.log("req.signedCookies >> ", req.signedCookies);

  /**
   * 서명 하지 않았다면
   * req.headers.cookie >>  my-cookie=keyboard
   * req.cookies >>  { 'my-cookie': 'keyboard' }
   * req.signedCookies >>  [Object: null prototype] {}
   * 
   * 서명 했다면
   * req.headers.cookie >>  my-cookie=s%3Akeyboard.3fEHCtxZ9k%2BZikEaKheY1Lgs9WN9ob9dSio%2BsEA%2FI6Y
   * req.cookies >>  {}
   * req.signedCookies >>  [Object: null prototype] { 'my-cookie': 'keyboard' }
   */

  res.json({ message: "쿠키 생성 완료!" });
});

app.listen(3000, () => {
  console.log("Running Server :3000");
});

(1)처럼 직접 헤더를 설정해도 되고 미들웨어인 cors를 이용하면 간단하게 헤더를 설정할 수 있습니다.
( cors()를 이용하면 매우 간단하지만 내부적으로 모든 처리를 하기 때문에 어떤 헤더를 작성하는지 알지 못하기 때문에 명시적으로 이렇게 동작한다는 것을 보여주기 위해서 직접 헤더를 설정하는 코드를 넣었습니다. )

단, 주의해야 할점은 Access-Control-Allow-Credentialstrue로 사용한다면 Access-Control-Allow-Origin는 구체적인 출처를 작성해야합니다. ( * 금지 )
( 즉, Access-Control-Allow-Credentials: true & Access-Control-Allow-Origin: *는 금지 )
( 쿠키를 전달하는 것이 보안적으로 위험하기 때문에 아무 대상에게나 전달하지 못하게 하기 위함인 것 같음 )

(2)처럼 직접 헤더를 설정해서 cookie를 생성할 수 있습니다.
하지만 express를 사용한다면 쉽게 res.cookie()를 이용해서 생성하고 옵션도 적용할 수 있습니다.

(3)을 적용하면 즉, 서명을 하면 쿠키의 값 앞/뒤에 특이한 값들이 붙습니다.
s%3A로 시작하고 .뒤에 이상한 문자열이 들어가 있습니다.
특정 서버에서 만든 쿠키임을 증명하기 위해서 서명한 것입니다.
서버에서 cookieParser() 미들웨어에 등록한 문자열을 통해서 해당 서버에서 만든 쿠키임을 증명하는 값을 생성해서 쿠키에 첨부해서 보냅니다.
따라서 사용자 임의로 쿠키의 내용을 바꾸면 서버에서는 해당 쿠키가 본인이 만든 쿠키가 아니라고 생각하고 쿠키를 통한 인증이 거부됩니다.

(4)를 허용하면 (3)에서 지정한 키를 이용해서 서명을 생성합니다.
(4)를 허용하고 (3)을 작성하지 않으면 오류가 발생합니다.
그리고 (3)(4)를 허용하면 (5)를 통해서 쿠키를 확인할 수 있습니다.
( 즉, 서명을 하면 req.cookies가 아닌 req.signedCookies에서 파싱된 쿠키를 확인할 수 있습니다. )

📮 레퍼런스

  1. « Node.js 교과서 4장, 6장 » ( 조현영 지음, 길벗, 2020 ) ( 4장 - cookie와 session, 6장 - cookie-parser )
  2. javascript.info - 쿠키와 document.cookie
  3. inpa - bodyParser / cookieParser 미들웨어 💯 사용법 정리

  4. 1-blue - Session
  5. 1-blue - Token
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.