9/7 Node.js 숙련 1주차 (3)

성준호·2024년 9월 7일
0

1. 쿠키와 세션

1) 쿠키와 세션이란?

쿠키

브라우저가 서버로부터 응답으로 Set-Cookie 헤더를 받은 경우 해당 데이터를 저장한 뒤 모든 요청에 포함하여 보낸다.

  • 쿠키는 사용자가 naver.com과 같은 웹사이트를 방문할 때마다 이전에 방문했던 정보를 기억하는 데이터 파일이다
  • 데이터를 여러 사이트에 공유할 수 있기 때문에 보안에 취약할 수 있다.
  • 쿠키는 userId=user-1321;userName=sparta와 같이 문자열 형식으로 존재하며 쿠키 간엔느 세미콜론으로 구분된다.

세션

쿠키를 기반으로 구성된 기술이다. 클라이언트가 마음대로 데이터를 확인할 수 있던 쿠키와는 다르게 세션은 데이터를 서버에만 저장한다.

  • 세션은 일반적으로 세션 id를 쿠키를 이용해 클라이언트에게 전달하여, 서버는 이 세션 id를 사용해 저장된 세션 데이터를 조회합니다.
  • 세션을 통해 사용자의 상태정보를 서버에 저장하면, 서버는 사용자의 상태를 추적할 수 있다.
  • 보안성은 좋으나, 반대로 사용자가 많은 경우 서버에 저장해야 할 데이터가 많아져 서버 컴퓨터가 감당하지 못하는 문제가 생긴다.
  • 쿠키와 마찬가지로 만료 기간이 있다.

2) 쿠키 만들어보기

서버가 클라이언트의 요청을 수신할 때 서버는 응답과 함께 Set-Cookie라는 헤더를 함께 전송할 수 있다. 그 후 쿠키는 서버에 의해 만들어진 응답과 함께 Cookie HTTP 헤더 안에 포함되어 전달 받는다.

// 'Set-Cookie'를 이용하여 쿠키를 할당하는 API
app.get("/set-cookie", (req, res) => {
  let expire = new Date();
  expire.setMinutes(expire.getMinutes() + 60); // 만료 시간을 60분으로 설정합니다.

  res.writeHead(200, {
    'Set-Cookie': `name=sparta; Expires=${expire.toGMTString()}; HttpOnly; Path=/`,
  });
  return res.end();
});

res.cookie()를 이용하여 쿠키 할당하기

// 'res.cookie()'를 이용하여 쿠키를 할당하는 API
app.get("/set-cookie", (req, res) => {
  let expires = new Date();
  expires.setMinutes(expires.getMinutes() + 60); // 만료 시간을 60분으로 설정합니다.

  res.cookie('name', 'sparta', {
    expires: expires
  });
  return res.end();
});

3) req를 이용하여 쿠키 접근하기

일반적으로 쿠키는 req.headers.cookie에 들어있다. req.headers는 클라이언트가 요청한 Request의 헤더를 의미합니다.

req.headers.cookie를 이용하여 쿠키 조회하기

// 'req.headers.cookie'를 이용하여 클라이언트의 모든 쿠키를 조회하는 API
app.get('/get-cookie', (req, res) => {
  const cookie = req.headers.cookie;
  console.log(cookie); // name=sparta
  return res.status(200).json({ cookie });
});

쿠키를 req.cookies 객체로 만들어준다. req.headers.cookie와 같이 번거로게 사용하지 않아도 된다.

# yarn을 이용해 cookie-parser를 설치합니다.
yarn add cookie-parser
app.use(cookieParser());
import cookieParser from 'cookie-parser';

app.use(cookieParser());

// 'req.cookies'를 이용하여 클라이언트의 모든 쿠키를 조회하는 API
app.get('/get-cookie', (req, res) => {
  const cookies = req.cookies;
  console.log(cookies);
  return res.status(200).json({ cookie: cookies });
});

쿠키의 형태가 name=sparta에서 { name: 'sparta' } 형태의 객체로 변환되었다.

5) 세션 만들어보기

쿠키에는 사용자가 누구인지 확실하게 구분할 수 있는 정보를 넣어야 한다. 그래야 민감한 정보는 서버에서만 관리하고, 사용자 식별 정보를 통해 사용자의 정보를 반환할 수 있게 된다.

/set-session을 호출했을 때 name=sparta의 정보를 서버에 저장하고, 저장한 시간 정보를 쿠키로 반환 받는 API,
get-session API를 호출했을 때 쿠키의 시간 정보를 이용하여 서버에 저장된 name 정보를 출력하는 API를 만들 수 있다.

/set-session API

let session = {};
app.get('/set-session', function (req, res, next) {
  // 현재는 sparta라는 이름으로 저장하지만, 나중에는 복잡한 사용자의 정보로 변경될 수 있습니다.
  const name = 'sparta';
  const uniqueInt = Date.now();
  // 세션에 사용자의 시간 정보 저장
  session[uniqueInt] = { name };

  res.cookie('sessionKey', uniqueInt);
  return res.status(200).end();
});
  • 서버에서 해당 사용자의 정보를 저장하기 위해 session 객체를 생성
  • set-session API가 호출되면 name=sparta의 정보를 세션에 삽입하고, 해당하는 데이터를 검색하기 위한 시간 정보를 쿠키로 반환

/get-session API

app.get('/get-session', function (req, res, next) {
  const { sessionKey } = req.cookies;
  // 클라이언트의 쿠키에 저장된 세션키로 서버의 세션 정보를 조회합니다.
  const name = session[sessionKey];
  return res.status(200).json({ name });
});
  • 쿠키에 저장된 sessionKey를 이용하여 session에 저장된 데이터를 불러온다.

6) 연습문제

요구사항
1. cookie-parser 미들웨어를 적용해주세요!
2. GET Method로 http://localhost:5001/set을 호출했을 때, name이라는 이름을 가진 “nodejs” 문자열을 저장한 쿠키를 반환해주세요.
3. GET Method로 http://localhost:5001/get을 호출했을 때, 클라이언트에게 전달받은 모든 쿠키 정보들이 반환되는 API를 만들어주세요!

import express from 'express';
import cookieParser from 'cookie-parser';

const app = express();
const PORT = 5001;

app.use(express.json());
app.use(cookieParser());

app.get("/set", (req, res) => {
  res.cookie('name', 'nodejs');
  return res.status(200).end();
});

app.get("/get", (req, res) => {
  const cookies = req.cookies;
  return res.status(200).json({ cookie: cookies });
});

app.listen(PORT, () => {
  console.log(PORT, '포트로 서버가 열렸어요!');
});

1. JWT

JWT(Json Web Token)은 웹 표준으로써, 서버와 클라이언트 사이에서 정보를 안전하게 전송하기 위해 도움을 주는 웹 토큰(Web Token)

1) 구조

JWT는 크게 세 부분 헤더, 페이로드, 서명으로 구성되어있다. 각각의 부분은 점(.)으로 분리된다.

  • Header: 토큰의 타입과 어떤 암호화를 사용하여 생성된 데이터인지 정의되어있다.
{
  "alg": "HS256",
  "typ": "JWT"
}
  • Payload: 실제 전달하려는 데이터를 담고있다. 개발자가 원하는 데이터를 저장한다.
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}
  • Signature: 헤더와 페이로드, 그리고 비밀 키를 이용하여 생성된다. 토큰이 변조되지 않은 정상적인 토큰인지 확인할 수 있게 도와준다.
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
	secret
)

각 부분을 Base64로 인코딩하여, 점(.)으로 연결하면 최종적으로 JWT를 생성하게 된다.
이렇게 생성된 JWT는 쿠키 또는 Path Parameter를 통해 전달될 수 있다.

2) 특성

  1. JWT는 비밀 키를 모르더라도 복호화가 가능하다

    • JWT를 가진 사람이라면 누구나 해당 토큰에 어떤 데이터가 담겨있는지 확인할 수 있다.
    • 변조만 불가능할 뿐, 누구나 복호화하여 볼 수 있다.
  2. 민감한 정보는 담지 않도록 한다.

    • JWT 페이로드는 누구나 복호화하여 볼 수 있기 때문이다.
  3. JavaScript와 같이 특정 언어에서만 사용 가능한 것은 아니다.

3) JWT와 쿠키, 세션의 차이점

데이터를 교환하고 관리하는 방식인 쿠키/세션과 달리, JWT는 단순히 데이터를 표현하는 형식이다.

  • JWT로 만든 데이터는 변조가 어렵고, 서버에 별도의 상태 정보를 저장하지 않기 때문에 서버를 Stateless(무상태)로 관리할 수 있다.
  • 쿠키와 세션은 사용자의 로그인 정보나 세션 데이터를 서버에 저장하므로 상태를 유지한다. 때문에 Stateful(상태보존)하게 데이터가 관리된다.
  • Node.js 서버가 언제든 죽었다 살아나도 똑같은 동작을 하면 Stateless하다고 볼 수 있다.
    반대로 서버가 죽었다 살아났을 때 조금이라도 동작이 다른 경우 Stateful하다고 볼 수 있다.

2. JWT 사용하기

1) JWT 라이브러리 설치

# yarn을 이용해 프로젝트를 초기화합니다.
yarn init -y

# jsonwebtoken, express 라이브러리를 설치합니다.
yarn add jsonwebtoken express

2) JSON 데이터 암호화

  • josnwebtoken 라이브러리의 sign 메서드를 사용해 JWT를 생성
import jwt from 'jsonwebtoken';

const token = jwt.sign({ myPayloadData: 1234 }, 'mysecretkey');
console.log(token); // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJteVBheWxvYWREYXRhIjoxMjM0LCJpYXQiOjE2OTA4NzM4ODV9.YUmYY9aef9HOO8f2d6Umh2gtWRXJjDkzjm5FPhsQEA0
  • sign 메서드는 첫 번째 인자로 Payload 데이터를, 두 번째 인자로 비밀 키를 받아 JWT를 생성한다.
    여기서 Payload는 문자열 뿐만 아니라, 객체도 할당할 수 있다.

3) 복호화하기

  • josnwebtoken 라이브러리의 decode 메서드를 사용해 JWT를 복호화
import jwt from 'jsonwebtoken';

const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJteVBheWxvYWREYXRhIjoxMjM0LCJpYXQiOjE2OTA4NzM4ODV9.YUmYY9aef9HOO8f2d6Umh2gtWRXJjDkzjm5FPhsQEA0";
const decodedValue = jwt.decode(token);

console.log(decodedValue); // { myPayloadData: 1234, iat: 1690873885 }

4) 변조되지 않은 데이터인지 검증

  • josnwebtoken 라이브러리의 verify 메서드를 사용해 JWT를 검증
import jwt from 'jsonwebtoken';

const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJteVBheWxvYWREYXRhIjoxMjM0LCJpYXQiOjE2OTA4NzM4ODV9.YUmYY9aef9HOO8f2d6Umh2gtWRXJjDkzjm5FPhsQEA0";
const decodedValueByVerify = jwt.verify(token, "mysecretkey");

console.log(decodedValueByVerify); // { myPayloadData: 1234, iat: 1690873885 }
  • JWT가 변조되지 않고, 올바른 비밀 키로 서명되었는지 검증한다.
  • 검증에 실패하면 에러가 발생한다.

잘못된 비밀 키로 검증

import jwt from 'jsonwebtoken';

const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJteVBheWxvYWREYXRhIjoxMjM0LCJpYXQiOjE2OTA4NzM4ODV9.YUmYY9aef9HOO8f2d6Umh2gtWRXJjDkzjm5FPhsQEA0";
const decodedValueByVerify = jwt.verify(token, "secretkey");

console.log(decodedValueByVerify);

// JsonWebTokenError: invalid signature

잘못된 비밀 키를 이용하여 검증하면 에러가 발생한다.

5) JWT를 사용하는 이유

JWT를 적용하지 않은 로그인 API

import express from 'express';
const app = express();

app.post('/login', function (req, res, next) {
  const user = { // 사용자 정보
    userId: 203, // 사용자의 고유 아이디 (Primary key)
    email: "archepro84@gmail.com", // 사용자의 이메일
    name: "이용우", // 사용자의 이름
  }

  res.cookie('sparta', user);  // sparta 라는 이름을 가진 쿠키에 user 객체를 할당합니다.
  return res.status(200).end();
});

app.listen(5002, () => {
  console.log(5002, "번호로 서버가 켜졌어요!");
});
  • 사용자의 정보가 sparta 이름을 가진 쿠키에 할당된다.
  • 쿠키의 속성값이나 만료 시간을 클라이언트가 언제든지 수정할 수 있다.
  • 쿠키의 위변조 여부를 확인할 수 없다.

JWT를 적용한 로그인 API

import express from 'express';
import JWT from 'jsonwebtoken';

const app = express();

app.post('/login', (req, res) => {
  // 사용자 정보
  const user = {
    userId: 203,
    email: 'archepro84@gmail.com',
    name: '이용우',
  };

  // 사용자 정보를 JWT로 생성
  const userJWT = JWT.sign(
    user, // user 변수의 데이터를 payload에 할당
    'secretOrPrivateKey', // JWT의 비밀키를 secretOrPrivateKey라는 문자열로 할당
    { expiresIn: '1h' }, // JWT의 인증 만료시간을 1시간으로 설정
  );

  // userJWT 변수를 sparta 라는 이름을 가진 쿠키에 Bearer 토큰 형식으로 할당
  res.cookie('sparta', `Bearer ${userJWT}`);
  return res.status(200).end();
});

app.listen(5002, () => {
  console.log(5002, '번호로 서버가 켜졌어요!');
});
  • 사용자의 정보를 Payload에 저장한 JWT를 sparta 이름을 가진 쿠키에 할당한다.
  • JWT를 생성할 때 위변조 여부를 확인할 수 있는 비밀 키를 사용하였다.
  • 쿠키의 만료 시간과 별개로 JWT의 만료 시간을 설정하였다.
profile
안녕하세요

0개의 댓글