[포스코x코딩온] KDT-Web-8 7주차 회고2 - Cookie & Session & JWT

Yunes·2023년 8월 15일
0

[포스코x코딩온]

목록 보기
18/47
post-thumbnail

📚 학습 동기

코딩온에서 Node.js 와 express 를 사용하여 Cookie 와 Session 을 어떻게 사용하는지 배웠다. 그런데 이런 생각이 들었다. 어떻게 사용하는지에 대해 배웠으면 이제는 에 초점을 둘 때가 아닐까?

쿠키와 세션은 왜 사용할까?

쿠키와 세션 다음 등장한 JWT 은 무엇이고 어떤 장점이 있기에 사용하는 것일까?

통신이 이뤄지는 것 같은데 HTTP 에 대해서 나는 잘 알고 있을까?

이번 포스트는 이러한 질문을 스스로에게 던지며 시작했다.

📘 Cookie 와 Session 은 왜 사용할까?

우리는 아주 자연스럽게 클라이언트와 서버사이에 무언가 소통이 이뤄진다고 생각한다. 그럼 이 통신은 어떻게 가능한걸까?

우선 몇가지 개념들을 짚고 넘어가야 할 것 같다.

주요 개념

프로토콜

  • 컴퓨터 내부, 컴퓨터 사이에서 데이터 교환 방식을 정의하는 규칙 체계

TCP(Transmission Control Protocol)

  • 두 개의 호스트를 연결하고 데이터를 교환하게 해주는 네트워크 프로토콜
  • 데이터와 패킷이 순서대로 전달되는 것을 보장
  • IP 와 함께 사용하는데 IP 가 데이터의 배달을 처리하고 TCP 는 패킷을 추적 및 관리한다.

HTTP

  • HTML 문서와 같은 리소스들을 가져올 수 있도록 해주는 프로토콜
  • 클라이언트 - 서버 구조
  • 브라우저인 클라이언트에 의해 전송되는 메세지를 요청(request) 라 부르고 서버에서 응답으로 전송되는 메시지를 응답(response) 라 부른다.
  • 비연결성 HTTP 메세지 connectionless
    • 클라이언트가 요청을 한뒤 응답을 받으면 연결을 끊는다.
    • 클라이언트가 request 를 서버에 보내면 서버는 클라이언트에게 요청에 맞는 response 를 보내고 접속을 끊는다.
  • 무상태 프로토콜 stateless
    • 통신이 끝나면 서버가 클라이언트의 상태를 보존하지 않는다.
    • 연결을 끊으면 클라이언트 - 서버 통신이 끝나며 상태 정보를 유지하지 않는다.
    • 서버의 확장성에 좋으나 클라이언트가 매번 추가 데이터를 전송해야 한다.
  • 클라이언트 - 서버 연결을 위해 신뢰할 수 있는 TCP 표준에 의존해 연결한다.

stateless, connectionless 두가지 특성에 대한 문제를 해결하기 위해 쿠키와 세션을 사용하게 되었다.

  • http/1.1 : 오늘날의 웹에서 널리 사용되고 있는 HTTP/1.1 프로토콜 통신
  • h2 : 2015 에 확정된 새로운 HTTP 프로토콜인 HTTP/2 프로토콜

즉, 클라이언트와 서버 사이에 데이터 교환을 하는 통신을 하기위해 여러 프로토콜을 사용할 수 있는데 그중 HTTP 프로토콜을 주로 사용해오고 있다. 이때 HTTP 는 stateless, connectionless 의 특징을 갖고 있기에 서버가 매번 클라이언트가 누구인지 확인해야 하는 문제를 보완하기 위해 쿠키와 세션을 사용하게 되었다.

Stateful vs Stateless

카페에서 점원에게 커피를 주문하는 상황을 생각해보자.

Stateful 은 내가 아는 지인인 A 점원에게 주문했고 이 점원이 커피를 만들어 줄 거라고 생각한다. 만약 중간에 다른 점원으로 바뀌면 이 상태 정보를 다른 점원에게 알려줘야 한다.

Stateless 는 누가 되었든 상관없다. 나는 어떤 점원이든 상관없이 커피만 받으면 된다.
커피만 받으면 되니 고객이 늘어나도 점원만 추가하면 요청을 처리할 수 있어 확장성에 좋다. 그러나 누구에게 커피를 전달해야 할지 매 요청마다 전달해야 한다.

📘 Cookie, Session 이 뭔데?

쿠키와 세션이 HTTP 가 갖는 무상태, 비연결성 특징을 보완한다는 사실을 알게 되었다.

그럼 쿠키와 세션이 이 문제점들을 어떻게 해결할까?

애초에 쿠키와 세션이 뭐지?

쿠키는 브라우저에서 정보를 저장하기 위한 방법을 제공한다.

이미지 출처 : learn.microsoft.com

쿠키는 브라우저에 의해 저장되는 키/값 쌍의 텍스트 문자열이다.

이미지 출처 : 어떻게 세션과 쿠키가 동작하는가?

개발자모드에서 애플리케이션 - 쿠키를 통해 현재 웹 페이지에서 저장하고 있는 쿠키의 종류와 값을 확인할 수 있다.

브라우저가 웹 페이지에 요청을 할때 서버는 쿠키를 생성해서 브라우저에게 response 의 일부로 반환한다. 지금까지는 client 에서

const express = require("express");
const cookieParser = require("cookie-parser");
const app = express();

/** cookie-parser */
// 일반쿠키
app.use(cookieParser());

app.get("/setCookie", (req, res) => {
  // 쿠키이름, 쿠키값, 옵션객체
  // cookie-parser 가 res.cookie 를 만들어준다.
  res.cookie("myCookie", "myValue", cookieConfig);
  res.send({ result: true, msg: "쿠키 생성 완료" });
});

위와 같은 방식으로 사용했기에 당연히 클라이언트에서 쿠키를 생성한다고 생각했었는데 사실 서버에서 쿠키를 생성해서 response 의 일부로 전달하는 것이었다. (애초에 위와 같은 router 코드는 서버에서 실행하는 건데 클라이언트에서 실행한다고 착각했었다.)

위의 코드에서는 cookie-parser 라는 미들웨어를 CommonJS 의 require 를 통해 가져와 사용하고 있는데 cookie-parser 미들웨어는 응답 객체에 cookie 메서드를 추가하여 쿠키를 쉽게 생성하고 조작할 수 있게 한다.

res.cookie("myCookie", "myValue", cookieConfig); : 이 코드는 res 객체에 프로퍼티로 cookie 를 추가하는것이라 생각했으나 실제론 response 객체에 cookie 메서드를 추가하여 세가지 파라미터로 쿠키이름, 쿠키값, config 를 전달하여 함수를 실행하고 쿠키를 생성하는 동작을 하고 있었다.

쿠키는 클라이언트 측인 브라우저에 저장된다. 웹 서버가 클라이언트로 보내는 응답 헤더중 Set-Cookie 라는 헤더에 키, 값을 보내면 그 응답을 받은 브라우저는 해당 쿠키를 저장하고 그 다음부터 자동으로 쿠키를 헤더에 넣어 송신한다.

Set-Cookie: <cookie-name>=<cookie-value>
Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>
Set-Cookie: <cookie-name>=<cookie-value>; Expires=<date>
Set-Cookie: <cookie-name>=<cookie-value>; HttpOnly
Set-Cookie: <cookie-name>=<cookie-value>; Max-Age=<number>
Set-Cookie: <cookie-name>=<cookie-value>; Partitioned
Set-Cookie: <cookie-name>=<cookie-value>; Path=<path-value>
Set-Cookie: <cookie-name>=<cookie-value>; Secure

Set-Cookie: <cookie-name>=<cookie-value>; SameSite=Strict
Set-Cookie: <cookie-name>=<cookie-value>; SameSite=Lax
Set-Cookie: <cookie-name>=<cookie-value>; SameSite=None; Secure

// Multiple attributes are also possible, for example:
Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>; Secure; HttpOnly

음... 뭔가 이상하다.

서버측에서는 Set-Cookie 문법에 따라 myCookie 라는 쿠키에 myValue 라는 값과 다른 조건들을 담아 정상적으로 쿠키를 만들어 줬는데 JSESSIONID, connect.sid 쟤들은 뭐지?

톰캣은 JSESSIONID 라는 이름으로 세션 아이디를 저장하고 node.js 는 connect.sid 라는 이름으로 세션을 저장한다.

앗.. 사실 내가 Java 프로젝트도 관리중이고 세션도 실행한 이후에 남은 세션이 JSESSIONID, connect.sid 라는 id 로 남아있던 거였다. 세션 삭제후 새로고침하니 내가 보낸 쿠키만 생성된다.

위의 코드에서는 res.cookie 를 통해 쿠키를 생성했지만 다음과 같은 방법으로도 쿠키를 만들어 줄 수 있다.

// cookie.ejs
function setCookie(name, value, days) {
  const date = new Date();
  date.setTime(date.getTime() + days * 10 * 1000);
  const expires = "expires=" + date.toUTCString();
  document.cookie = name + "=" + value + ";" + expires + ";path=/";
}

브라우저 (클라이언트) 에서 원래는 서버로부터 응답헤더를 통해 이미 생성되어 전달된 쿠키를 저장한다고 하면 이 방식은 브라우저에서 document 객체에 에 직접 cookie 프로퍼티를 추가해서 마치 응답헤더를 통해 name, value 값과 config 로 만료기간과 path 를 전달받아 cookie 를 생성한 것처럼 처리하는 코드이다.

아무튼 정리하자면 쿠키는 서버에서 응답 헤더에 Set-Cookie 를 통해 생성해줄 수 있는데 기본적으로 이름, 값을 전달해야 하며 설정으로 domain, expires, httponly, path, secure 등의 조건을 담아 전달할 수 있다. 만료기간동안 클라이언트 측인 브라우저의 로컬에서 쿠키를 저장하고 있기에 다음 요청을 할때마다 쿠키를 요청 헤더에 담아 전달하니 일정기간동안 특정 정보를 저장할 수 있는 것이다.

쿠키 좋은데? 문제는 없나?

만료기간을 설정하지 않은 쿠키를 생각해보자. 이 쿠키는 휘발되지 않는다. 그럼 해커가 참 좋아할 것 같다. 또한 쿠키는 자동으로 매 요청마다 헤더에 실려 서버로 전송되는데 쿠키에 저장된 정보가 많아지면 성능에 문제가 발생할 수 있어 쿠키의 데이터는 일정 용량으로 제한된다.

세션은 쿠키와 달리 정보를 서버측에 저장한다.

이미지 출처 : Harvard Extension School

세션은 서버에서 클라이언트를 구분하기 위해 세션 ID 를 부여하여 웹 브라우저가 서버에 접속하여 브라우저를 종료할 때까지 인증상태를 유지하는 기술이다.

가만.. HTTP 는 stateless 인데 이거 어떻게 정보를 유지하는 걸까?

세션도 쿠키를 사용한다. 다만 SessionID 만 쿠키에 넣어 저장한다.

맨 처음 사용자가 HTTP 요청의 Body 에 사용자 정보를 담아 보내면 서버는 해당 정보가 유효할시 사용자와 데이터를 식별하는 SessionID 를 생성하고 Set-Cookie 헤더에 세션 ID 를 실어 보낸다. 브라우저 (클라이언트) 는 이 세션 ID 로 쿠키를 만들어 로컬에 저장한다. 서버는 처음 요청을 받았을때 SessionID 를 생성할 뿐 아니라 다른 데이터를 서버의 메모리에 저장한다.

라이프 사이클에 있어 쿠키와 세션을 비교하자면 둘다 만료시간이 있다. 그러나 쿠키는 파일로 저장되기에 브라우저를 종료해도 정보가 계속 남아있을 수 있고 세션은 만료시간이 남아 있어도 브라우저가 종료되면 삭제된다.

보안 측면에서 데이터를 서버에 저장하니 더 안전하다. 그러나 서버의 리소스를 사용하여 데이터를 저장하니 용량이 커질수록 부담이 될 수 있다. 그래서 세션은 사용자의 수 만큼 서버 메모리를 차지해서 이런 문제를 보완한 토큰 기반의 인증방식을 사용하는 추세이다.

어플리케이션을 보면 로컬 스토리지와 세션 스토리지가 있다.

로컬 스토리지는 만료되지 않고 계속 남아있으나 세션 스토리지는 페이지의 세션이 종료되면 사라진다.

session storage 사용법

// Save data to sessionStorage
sessionStorage.setItem("key", "value");

// Get saved data from sessionStorage
let data = sessionStorage.getItem("key");

// Remove saved data from sessionStorage
sessionStorage.removeItem("key");

// Remove all saved data from sessionStorage
sessionStorage.clear();

토큰 기반의 인증방식인 JWT (JSON Web Token) 은 어떻게 세션의 문제를 개선했나?

이미지 출처 : codespot - Token vs Session Authentication

앞에서 살펴본 것처럼 쿠키기반 인증은 맨 처음 브라우저가 요청을 보내면 서버는 이에 대해 세션을 생성하여 SessionID 를 전송해 그 다음부터 브라우저는 헤더에 SessionID 가 담긴 쿠키를 전송해왔다.

현대에 이르어 토큰 기반의 인증은 맨 처음 요청은 이전과 같다. 다만 이에 대한 응답으로 서버는 JSON Web Token (JWT) 이라 불리는 토큰을 생성하여 클라이언트에게 보낸다. JWT 는 일반적으로 로컬 스토리지나 쿠키에 저장된다. 이후 유저에 의한 모든 요청은 이 JWT 토큰을 포함한다.

JWT 토큰엔 2가지 종류가 있다. Access Token 과 Refresh Token 이 바로 그것이다.

맨 처음 유저의 요청과 body 에 담긴 정보를 받았을때 이에 대해 Access Token 과 Refresh Token 을 생성한다. Access Token 은 일반적으로 만료 기간이 짧고 Refresh Token 을 통해 Access Token 을 새로 생성할 수 있다.

클라이언트는 이 Access Token 을 로컬 스토리지나 쿠키에 저장한다. 매 요청마다 클라이언트는 JWT 토큰을 요청 헤더의 Authorization 필드에 담아 type 을 Bearer 로 설정하여 전달한다.

JWT 토큰도 쿠키나 로컬 스토리지에 저장되니 보안에 취약하다고 생각할 수 있다. JWT 토큰도 로컬 스토리지에 저장되면 보안에 취약할 수 있고 Base64 로 인코딩 되어 있으니 누구나 복호화할 수 있으나 Signature 에 서명이 존재하여 암호화 방식과 서명값을 갖고 있어야 JWT 토큰에 담긴 데이터를 알 수 있어 이런 문제를 어느정도 보완한 것 같다.

참고로 JWT 토큰은 jwt.io 에 토큰을 넣으면 복호화할 수 있다. 암호화 되어 있는 경우 secret 을 알고 있다면 복호화할 수 있다.

📔 레퍼런스

docs
mdn - 프로토콜
mdn - HTTP Cookie
mdn - Set-Cookie
mdn - session storage
blog
RyanGomdoriPhho - 쿠키와 세션
prohannah - HTTP
hudi - cookie & session
koh1018 - sesseion vs token vs cookie, JWT
reference
codespot - Token vs Session Authentication

profile
미래의 나를 만들어나가는 한 개발자의 블로그입니다.

0개의 댓글