39일차 Node.js

정상희·2025년 5월 15일

코딩공부

목록 보기
49/60
post-thumbnail

현재 오즈코딩스쿨 강의를 통해 프론트엔드를 학습하고 있습니다.
본 포스트는 해당 강의에 대한 내용 정리를 목적으로 합니다.

1. Cookie


웹에서 말하는 Cookie(쿠키)는 웹사이트가 사용자의 브라우저에 저장하는 작은 데이터 조각이다.

1) 역할


a. 세션 유지

로그인 상태를 기억하게 해줘요. 예: 로그인하면 쿠키에 정보가 저장되어, 새로고침하거나 페이지를 이동해도 로그인이 유지.

b. 사용자 맞춤 설정 저장

예: 다크 모드 설정, 언어 설정 등을 저장해서 다음 방문 시 자동으로 적용

c. 사용자 행동 추적

방문 기록, 클릭 패턴 등을 분석해서 마케팅, 광고 등에 사용


2) 쿠키의 구성

  • 이름 (Name)
  • 값 (Value)
  • 만료 날짜 (Expires)
  • 도메인 (어떤 사이트에서 생성됐는지)
  • 경로 (어떤 경로에 접근할 때 쿠키가 전달될지)
  • 보안 여부 (Secure, HttpOnly 같은 옵션)

3) 예시

document.cookie = "username=Danaka; expires=Fri, 31 Dec 2025 23:59:59 GMT; path=/";

이 코드는 username=Danaka라는 쿠키를 저장하고, 2025년 12월 31일까지 유지되도록 설정


4) 실습

a. 모듈 설치

npm init -y
npm i express
npm i express cors cookie-parser

b. 코드

cookie.js

// HTML 문서에서 ID가 'set_cookie'인 요소를 찾아서 setCookieButton 변수에 할당합니다.
// 이 요소는 아마도 "쿠키 설정" 버튼
const setCookieButton = document.querySelector('#set_cookie');

// HTML 문서에서 ID가 'delete_cookie'인 요소를 찾아서 deleteCookieButton 변수에 할당합니다.
// 이 요소는 아마도 "쿠키 삭제" 버튼
const deleteCookieButton = document.querySelector('#delete_cookie');

// axios의 기본 설정에 withCredentials를 true로 설정합니다.
// 이 설정은 브라우저가 교차 출처 요청(cross-origin requests)을 보낼 때
// 자동으로 쿠키, 인증 헤더 등의 자격 증명을 포함하도록 지시합니다.
// 쿠키를 사용한 세션 관리 등에서 필수적인 설정입니다.
axios.defaults.withCredentials = true;

// setCookieButton을 클릭했을 때 실행될 이벤트 핸들러를 정의합니다.
setCookieButton.onclick = () => {
  // http://localhost:3000으로 GET 요청을 보냅니다.
  // 이 요청은 서버(http://localhost:3000)에게 쿠키를 설정하라는 신호 역할을 할 것으로 예상됩니다.
  // 서버는 이 GET 요청에 대한 응답으로 'Set-Cookie' 헤더를 포함하여 쿠키를 브라우저에 설정하게 될 것입니다.
  axios.get('http://localhost:3000')
  .then(res => {
    // 요청이 성공적으로 완료되면 서버로부터 받은 응답(res) 객체를 콘솔에 출력합니다.
    // 응답 상태 코드나 데이터 등을 확인할 수 있습니다.
    console.log(res);
  })
  // .catch(...)를 추가하여 오류 처리 로직을 구현하는 것이 좋습니다.
  // 예: .catch(error => console.error('쿠키 설정 실패:', error));
}

// deleteCookieButton을 클릭했을 때 실행될 이벤트 핸들러를 정의합니다.
deleteCookieButton.onclick = () => {
  // http://localhost:3000으로 DELETE 요청을 보냅니다.
  // 이 요청은 서버(http://localhost:3000)에게 쿠키를 삭제하라는 신호 역할을 할 것으로 예상됩니다.
  // 서버는 이 DELETE 요청을 받고 해당 쿠키를 만료시키거나 삭제하는 로직을 수행해야 합니다.
  axios.delete('http://localhost:3000')
  .then(res => {
    // 요청이 성공적으로 완료되면 서버로부터 받은 응답(res) 객체를 콘솔에 출력합니다.
    // 응답 상태 코드 등을 확인할 수 있습니다.
    console.log(res);
  })
   // .catch(...)를 추가하여 오류 처리 로직을 구현하는 것이 좋습니다.
   // 예: .catch(error => console.error('쿠키 삭제 실패:', error));
}

server.js

// express 모듈을 불러와 웹 서버를 구성하는 데 사용합니다.
const express = require('express');
// cors 모듈을 불러와 교차 출처 요청(Cross-Origin Requests)을 처리합니다.
// 클라이언트(예: localhost:9000)와 서버(localhost:3000)의 출처가 다르므로 필요합니다.
const cors = require('cors');
// cookie-parser 모듈을 불러와 요청 헤더의 쿠키를 파싱하고 응답에 쿠키를 설정하는 데 도움을 줍니다.
const cookieParser = require('cookie-parser');

// express 애플리케이션 인스턴스를 생성합니다.
const app = express();

// CORS 미들웨어를 애플리케이션에 적용합니다.
app.use(cors({
  // origin: 허용할 클라이언트 출처(Origin) 목록입니다. 클라이언트 코드가 실행되는 주소와 포트를 명시합니다.
  // 여기서는 127.0.0.1:9000 또는 localhost:9000에서 오는 요청만 허용합니다.
  origin: ['http://127.0.0.1:9000', 'http://localhost:9000'],
  // methods: 허용할 HTTP 메소드 목록입니다. 클라이언트에서 사용하는 GET, DELETE 메소드와 CORS preflight 요청을 위한 OPTIONS를 허용합니다.
  methods: ['GET', 'DELETE', 'OPTIONS'],
  // credentials: true 설정은 브라우저가 요청 시 쿠키나 인증 헤더 같은 자격 증명을 포함하도록 허용합니다.
  // 클라이언트와 서버 모두에서 이 설정을 true로 해야 쿠키 기반 통신이 정상적으로 동작합니다.
  credentials: true
}));

// cookie-parser 미들웨어를 적용합니다.
// 이 미들웨어는 들어오는 요청의 Cookie 헤더를 파싱하여 req.cookies 객체로 만들어주고,
// res.cookie(), res.clearCookie() 등의 메서드를 사용할 수 있게 해줍니다.
app.use(cookieParser());

// 루트 경로('/')에 대한 GET 요청을 처리하는 라우트를 정의합니다.
// 클라이언트의 '쿠키 설정' 요청을 여기서 처리합니다.
app.get('/', (req, res) => {
  // 'test-cookie'라는 이름으로 쿠키를 설정합니다.
  // 값은 'my cookie'입니다.
  // 옵션 설명:
  // maxAge: 쿠키의 최대 수명(밀리초)입니다. 100000ms(100초)로 설정됩니다.
  // httpOnly: true로 설정하면 클라이언트 측 JavaScript에서 document.cookie 등으로 쿠키에 접근할 수 없습니다. 
  // XSS 공격 방지에 도움이 되는 중요한 보안 설정입니다.
  // secure: true로 설정하면 HTTPS 연결에서만 쿠키가 전송됩니다. 
  // 로컬 환경(http://localhost)에서 테스트 시 secure: true이면 쿠키가 설정되지 않거나 전송되지 않을 수 있으니 주의하세요. 
  //로컬 테스트 시에는 이 옵션을 제거하거나 false로 설정할 필요가 있습니다.
  res.cookie('test-cookie', 'my cookie', { maxAge: 100000, httpOnly: true, secure: true });

  // 클라이언트에 '쿠키 생성 완료' 메시지를 응답으로 보냅니다.
  res.send('쿠키 생성 완료');
});

// 루트 경로('/')에 대한 DELETE 요청을 처리하는 라우트를 정의합니다.
// 클라이언트의 '쿠키 삭제' 요청을 여기서 처리합니다.
app.delete('/', (req, res) => {
  // 'test-cookie'라는 이름의 쿠키를 삭제(무효화)합니다.
  // 실제로는 해당 쿠키의 만료일을 현재보다 과거로 설정하여 브라우저가 즉시 삭제하도록 지시합니다.
  res.clearCookie('test-cookie');

  // 클라이언트에 '쿠키 삭제 완료' 메시지를 응답으로 보냅니다.
  res.send('쿠키 삭제 완료');
});

// 3000번 포트에서 서버를 시작합니다.
app.listen(3000, () => {
  // 서버가 성공적으로 시작되면 콘솔에 메시지를 출력합니다.
  console.log('서버 실행!');
});



2. Session

Session은 서버가 사용자(클라이언트)의 정보를 일정 시간 동안 유지하기 위해 사용하는 저장 방식
즉, 사용자와 서버 간의 "연결된 상태"를 유지하기 위한 기술이다.

1) 세션 작동 원리


  • 사용자가 로그인 요청 등을 하면 서버는 고유한 세션 ID를 생성하고,
  • 그 ID를 클라이언트의 쿠키에 저장 (Set-Cookie: sessionId=abc123)
  • 이후 클라이언트가 요청할 때마다 이 세션 ID를 보내면
  • 서버는 세션 ID를 보고 해당 사용자 정보를 찾아냄

즉, 세션 ID는 쿠키에 저장되지만, 실제 데이터(예: 로그인한 유저 정보)는 서버에 저장

쿠키는 클라이언트에 저장, 세션은 서버에 저장
쿠키보다 보안이 뛰어남
로그인 등 보안이 필요한 경우에 자주 사용


2) 세션 관리 방법

a. 서버 측 세션 저장소

  • 메모리 (예: Express의 기본 메모리 저장소)
  • 파일 시스템
  • Redis, Memcached (대용량 서비스에 적합)
  • 데이터베이스 (MySQL, MongoDB 등)

b. 세션 만료 설정

  • 사용자가 일정 시간 동안 활동이 없으면 세션이 만료됩니다.
    예: 30분 동안 활동 없으면 로그아웃 처리

c. 보안 고려사항

  • 세션 ID는 무작위로 생성해 예측이 어렵게 해야 함
  • HTTPS를 사용하여 세션 ID 유출 방지
  • HttpOnly, Secure, SameSite 옵션을 쿠키에 적용

3) 예시

const express = require('express');
const session = require('express-session');

const app = express();

app.use(session({
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: true,
  cookie: { maxAge: 60000 } // 1분
}));

app.get('/', (req, res) => {
  if (req.session.user) {
    res.send(`환영합니다, ${req.session.user.name}`);
  } else {
    res.send('로그인이 필요합니다.');
  }
});

app.get('/login', (req, res) => {
  req.session.user = { name: '홍길동' };
  res.send('로그인 성공');
});

app.get('/logout', (req, res) => {
  req.session.destroy(() => {
    res.send('로그아웃 완료');
  });
});

4) 실습

a. 모듈 설치

npm init -y
npm i express cors cookie-parser express-session

b. 코드

login.js

// DOM(Document Object Model) 요소를 선택합니다.
// 폼(form) 관련 요소들
const form = document.querySelector('form'); // HTML 문서의 첫 번째 form 요소를 선택합니다.
const idInput = document.querySelector('#user_id'); // ID가 'user_id'인 요소를 선택합니다 (사용자 ID 입력 필드).
const passwordInput = document.querySelector('#user_password'); // ID가 'user_password'인 요소를 선택합니다 (비밀번호 입력 필드).
const loginButton = document.querySelector('#login_button'); // ID가 'login_button'인 요소를 선택합니다 (로그인 버튼).

// 메인 콘텐츠 및 사용자 정보 관련 요소들
const main = document.querySelector('main'); // HTML 문서의 첫 번째 main 요소를 선택합니다 (사용자 정보 등을 표시할 영역).
const userName = document.querySelector('#user_name'); // ID가 'user_name'인 요소를 선택합니다 (사용자 이름을 표시할 요소).
const userInfo = document.querySelector('#user_info'); // ID가 'user_info'인 요소를 선택합니다 (추가 사용자 정보를 표시할 요소).
const logoutButton = document.querySelector('#logout_button'); // ID가 'logout_button'인 요소를 선택합니다 (로그아웃 버튼).

// Axios 설정
// 모든 Axios 요청에 자격 증명(예: 쿠키, 인증 헤더)을 포함하도록 설정합니다.
// 이는 서버와의 세션 관리에 중요하며, 특히 CORS 환경에서 필요할 수 있습니다.
axios.defaults.withCredentials = true;

// 이벤트 리스너: 폼 제출
// 폼의 'submit' 이벤트가 발생했을 때 기본 동작(페이지 새로고침)을 막습니다.
// 이렇게 하면 JavaScript를 통해 비동기적으로 로그인 처리를 할 수 있습니다.
form.addEventListener('submit', (e) => e.preventDefault());

// 로그인 요청 함수
// 사용자 ID와 비밀번호를 입력 필드에서 가져와 Axios POST 요청으로 서버에 전송합니다.
// 서버 주소는 'http://localhost:3000'으로 하드코딩되어 있습니다.
function login() {
  const userId = idInput.value; // ID 입력 필드의 현재 값을 가져옵니다.
  const userPassword = passwordInput.value; // 비밀번호 입력 필드의 현재 값을 가져옵니다.

  // 'http://localhost:3000' 엔드포인트로 POST 요청을 보냅니다.
  // 요청 본문에는 userId와 userPassword 객체가 JSON 형태로 포함됩니다.
  return axios.post('http://localhost:3000', { userId, userPassword });
}

// 로그아웃 요청 함수
// 서버에 로그아웃 요청을 보냅니다. DELETE 메서드를 사용하고 있습니다.
// 서버 주소는 'http://localhost:3000'으로 하드코딩되어 있습니다.
function logout() {
  // 'http://localhost:3000' 엔드포인트로 DELETE 요청을 보냅니다.
  return axios.delete('http://localhost:3000');
}

// 사용자 정보 가져오기 함수
// 서버로부터 현재 로그인된 사용자의 정보를 가져옵니다. GET 메서드를 사용하고 있습니다.
// 서버 주소는 'http://localhost:3000'으로 하드코딩되어 있습니다.
function getUserInfo() {
  // 'http://localhost:3000' 엔드포인트로 GET 요청을 보냅니다.
  return axios.get('http://localhost:3000');
}

// 사용자 정보 표시 함수
// 전달받은 사용자 정보를 UI에 렌더링하고, 폼을 숨기고 메인 영역을 표시합니다.
function renderUserInfo(user) {
  main.style.display = 'block'; // main 영역을 보이게 합니다.
  form.style.display = 'none'; // 폼 영역을 숨깁니다.
  userName.textContent = user.user_name; // 사용자 이름을 표시합니다.
  userInfo.textContent = user.user_info; // 추가 사용자 정보를 표시합니다.
}

// 로그인 폼 표시 함수
// 사용자 정보 영역을 숨기고 로그인 폼을 표시합니다. 사용자 정보 필드는 초기화합니다.
function renderLoginForm() {
  main.style.display = 'none'; // main 영역을 숨깁니다.
  form.style.display = 'block'; // 폼 영역을 보이게 합니다.
  userName.textContent = ''; // 사용자 이름 필드를 비웁니다.
  userInfo.textContent = ''; // 추가 사용자 정보 필드를 비웁니다.
}

// 이벤트 핸들러: 로그인 버튼 클릭
// 로그인 버튼 클릭 시 실행되는 함수입니다.
loginButton.onclick = () => {
  // login() 함수를 호출하여 로그인 요청을 보냅니다. 이는 Promise를 반환합니다.
  login()
    // 로그인 요청이 성공하면 (Promise resolve) getUserInfo() 함수를 호출하여 사용자 정보를 가져옵니다.
    .then(() => getUserInfo())
    // 사용자 정보 가져오기 요청이 성공하면 (Promise resolve) 응답 데이터를 받아 renderUserInfo 함수에 전달하여 UI를 업데이트합니다.
    .then(res => renderUserInfo(res.data))
    // TODO: 에러 처리가 누락되어 있습니다. 로그인 또는 사용자 정보 가져오기 실패 시 사용자에게 알리거나 UI를 변경하는 로직이 필요합니다.
    // .catch(error => {
    //   console.error('로그인 또는 사용자 정보 가져오기 실패:', error);
    //   // 예: 사용자에게 에러 메시지 표시
    // });
}

// 이벤트 핸들러: 로그아웃 버튼 클릭
// 로그아웃 버튼 클릭 시 실행되는 함수입니다.
logoutButton.onclick = () => {
  // logout() 함수를 호출하여 로그아웃 요청을 보냅니다. 이는 Promise를 반환합니다.
  logout()
    // 로그아웃 요청이 성공하면 (Promise resolve) 응답 데이터를 받아 처리합니다.
    .then(res => {
      console.log(res); // 콘솔에 응답을 출력합니다 (디버깅용).
      renderLoginForm(); // 로그인 폼을 다시 표시합니다.
    })
    // TODO: 에러 처리가 누락되어 있습니다. 로그아웃 실패 시 처리 로직이 필요합니다.
    // .catch(error => {
    //   console.error('로그아웃 실패:', error);
    //   // 예: 사용자에게 에러 메시지 표시
    // });
}

server.js

// 필요한 모듈들을 가져옵니다.
const express = require('express') // 웹 서버 프레임워크 Express
const cors = require('cors') // CORS (Cross-Origin Resource Sharing) 미들웨어
const session = require('express-session') // 세션 관리 미들웨어
const cookieParser = require('cookie-parser') // 쿠키 파싱 미들웨어

// 사용자 데이터 (임시로 메모리에 저장)
// 실제 서비스에서는 데이터베이스를 사용해야 합니다.
// 보안 경고: 비밀번호를 평문으로 저장하는 것은 매우 위험합니다. 반드시 해싱하여 저장해야 합니다.
const users = [
  {
    user_id: 'test',
    user_password: '1234', // 보안 경고: 평문 비밀번호! 해싱 필요!
    user_name: '테스트 유저',
    user_info: '테스트 유저입니다'
  }
]

// Express 애플리케이션 인스턴스를 생성합니다.
const app = express()

// 미들웨어 설정

// CORS 설정: 클라이언트의 특정 Origin(주소)에서의 요청을 허용합니다.
// credentials: true 설정은 클라이언트에서 인증 정보(쿠키 등)를 함께 보내도록 허용합니다.
app.use(cors({
  origin: ['http://127.0.0.1:9000', 'http://localhost:9000'], // 허용할 클라이언트 주소 목록
  methods: ['OPTIONS', 'POST', "GET", "DELETE"], // 허용할 HTTP 메서드 목록
  credentials: true // 쿠키 등 자격 증명 정보 교환 허용
}))

// cookie-parser 미들웨어: 요청의 쿠키를 파싱하여 req.cookies 객체로 만듭니다.
app.use(cookieParser())

// express.json 미들웨어: 요청의 본문(body)이 JSON 형식일 경우 파싱하여 req.body 객체로 만듭니다.
app.use(express.json())

// express-session 미들웨어: 세션을 관리합니다.
app.use(session({
  secret: 'session secret', // 세션 ID 쿠키를 서명하는 데 사용되는 비밀 키. 보안을 위해 무작위의 복잡한 문자열을 사용하고 환경 변수로 관리하는 것이 좋습니다.
  resave: false, // 세션 데이터가 변경되지 않아도 세션을 저장할지 여부 (일반적으로 false 권장)
  saveUninitialized: false, // 초기화되지 않은(새로 생성되었지만 수정되지 않은) 세션을 저장소에 저장할지 여부 (일반적으로 false 권장)
  name: 'session_id' // 클라이언트에게 전송될 세션 쿠키의 이름
}))

// 라우트 정의

// POST / : 로그인 처리
app.post('/', (req, res) => {
  // 요청 본문(req.body)에서 userId와 userPassword를 가져옵니다. (express.json 미들웨어 덕분)
  const { userId, userPassword } = req.body

  // users 배열에서 제출된 ID와 비밀번호가 일치하는 사용자를 찾습니다.
  const userInfo = users.find(el => el.user_id === userId && el.user_password === userPassword)

  // 사용자가 찾아지지 않으면 (로그인 실패)
  if (!userInfo) {
    res.status(401).send('로그인 실패') // 401 Unauthorized 상태 코드와 메시지를 응답합니다.
  } else {
    // 사용자를 찾았으면 (로그인 성공)
    // 세션에 사용자 ID를 저장합니다. 이로써 사용자가 로그인 상태임을 세션을 통해 관리합니다.
    req.session.userId = userInfo.user_id
    res.send("세션 생성 완료!") // 성공 메시지를 응답합니다.
  }
})

// GET / : 사용자 정보 가져오기
app.get('/', (req, res) => {
  // 현재 세션의 userId를 사용하여 users 배열에서 해당 사용자를 찾습니다.
  // req.session.userId가 존재하지 않으면 (로그인되지 않은 상태) userInfo는 undefined가 됩니다.
  const userInfo = users.find(el => el.user_id === req.session.userId)

  // TODO: 로그인되지 않은 상태(userInfo가 undefined인 경우)에 대한 명시적인 처리가 필요합니다.
  // 현재는 userInfo가 undefined여도 res.json(undefined)를 보내게 되며, 이는 HTTP 200 OK와 본문 null로 응답됩니다.
  // 일반적으로는 로그인되지 않은 경우 401 Unauthorized 상태 코드를 응답하는 것이 좋습니다.
  // if (!userInfo) {
  //   return res.status(401).send('로그인 정보 없음');
  // }

  // 찾은 사용자 정보를 JSON 형식으로 응답합니다.
  return res.json(userInfo)
})

// DELETE / : 로그아웃 처리
app.delete('/', (req, res) => {
  // 세션을 파기(삭제)합니다. 서버 메모리/저장소에서 세션 데이터가 제거됩니다.
  req.session.destroy()
  // 클라이언트의 세션 쿠키를 삭제하도록 지시합니다.
  // express-session이 destroy() 시 쿠키를 만료시키기도 하지만 명시적으로 지우는 코드를 두는 경우도 있습니다.
  res.clearCookie('session_id')
  res.send("세션 삭제 완료!") // 성공 메시지를 응답합니다.
})

// 서버를 3000번 포트에서 실행합니다.
app.listen(3000, () => console.log('서버 실행!'))
profile
UI/UX디자이너의 코딩 공부

0개의 댓글