Node.js

[ Node ] 07. CORS, Helmet ,Day.js, Nodemon, JWT

변쌤(이젠강남) 2024. 9. 28. 16:13
반응형

CORS, Helmet ,Day.js, Nodemon

 

1. CORS (교차 출처 리소스 공유)

CORS는 웹 서버의 리소스(예: API)를 다른 도메인의 웹 페이지에서 접근할 수 있게 해주는 메커니즘입니다. 기본적으로 웹 브라우저는 보안상의 이유로 이러한 교차 출처 요청을 제한합니다. CORS는 서버가 명시적으로 다른 도메인에서 요청을 허용할 수 있는 방법을 정의합니다.

  • 주로 사용하는 경우: 백엔드 서비스에서 다른 도메인에 있는 프론트엔드 애플리케이션이 백엔드 API에 접근할 수 있도록 허용할 때 주로 사용합니다.
  • Express (Node.js)에서 사용 예시
const cors = require('cors');
const express = require('express');
const app = express();

app.use(cors()); // 모든 도메인에서 서버에 접근 허용

app.listen(3000);

 

 

2. Helmet

Helmet은 Express.js 애플리케이션을 보호하기 위해 다양한 HTTP 헤더를 설정해주는 보안 패키지입니다. Helmet은 XSS(사이트 간 스크립팅)나 클릭재킹 등의 일반적인 웹 취약점으로부터 애플리케이션을 보호하는 데 도움을 줍니다.

  • 주로 사용하는 경우: 서버의 보안을 강화하여 공격에 취약하지 않도록 보호하고 싶을 때 사용합니다.
  • Express에서 사용 예
const helmet = require('helmet');
const express = require('express');
const app = express();

app.use(helmet()); // 다양한 보안 헤더를 자동으로 설정

app.listen(3000);

 

 

3. Day.js

Day.js는 날짜와 시간을 다루기 위한 경량 자바스크립트 라이브러리입니다. Moment.js와 유사하지만 훨씬 가볍고 빠릅니다. 날짜와 시간을 쉽게 포맷하거나, 시간 차이를 계산하는 등의 작업을 할 수 있습니다.

  • 주로 사용하는 경우: 날짜를 포맷하거나, 시간 간격을 계산하고 싶을 때 사용합니다.
  • 사용 예
const dayjs = require('dayjs');

console.log(dayjs().format()); // 현재 날짜와 시간을 ISO 형식으로 출력
console.log(dayjs().add(1, 'day').format('YYYY-MM-DD')); // 하루를 더한 날짜 출력

 

 

4. Nodemon

Nodemon은 Node.js 애플리케이션을 자동으로 다시 시작해주는 개발 도구입니다. 코드를 수정할 때마다 수동으로 서버를 다시 시작할 필요 없이, 변경 사항을 감지해 자동으로 서버를 재시작해줍니다.

  • 주로 사용하는 경우: 개발 중에 코드를 변경할 때마다 서버를 수동으로 재시작하지 않도록 자동화하고 싶을 때 사용합니다.
  • 사용 예시: nodemon을 설치한 후 nodemon app.js 명령어로 실행하면, app.js 파일이 변경될 때마다 서버가 자동으로 재시작됩니다.

 

 


 

 

# Sequelize

ORM(Object-Relational Mapping) 라이브러리로, MySQL, PostgreSQL, MariaDB, SQLite, Microsoft SQL Server 등을 지원합니다. SQL 쿼리를 직접 작성하지 않고 자바스크립트 객체를 사용해 데이터베이스를 조작할 수 있게 도와줍니다.

Sequelize는 테이블을 자바스크립트 객체로 매핑하여 데이터베이스 작업을 더 직관적으로 관리할 수 있게 해줍니다.

 

 

  • Sequelize 설정을 통해 데이터베이스와 연결합니다.
  • 모델 정의를 통해 MySQL의 테이블을 자바스크립트 객체로 표현합니다.
  • CRUD 작업을 위해 Sequelize의 메서드를 사용하여 데이터를 삽입, 조회, 수정, 삭제합니다.
  • 테이블 간의 **연관 관계(associations)**를 설정하여 더 복잡한 데이터 구조를 관리할 수 있습니다.

 

 

 

Sequelize의 특징

  1. ORM (Object-Relational Mapping): 자바스크립트 객체와 데이터베이스 테이블을 매핑해주어 SQL 쿼리 없이 데이터를 조회하고 수정할 수 있습니다.
  2. 모델 정의: 테이블을 모델로 정의하고, 각 컬럼의 타입과 특성을 설정할 수 있습니다.
  3. Associations(연관 관계): 테이블 간의 관계(1:1, 1:다, 다:다)를 설정할 수 있습니다.
  4. 쿼리 빌더: 객체 기반으로 다양한 쿼리를 작성할 수 있습니다.

 

1. Sequelize 설치 및 설정

Sequelize와 MySQL 드라이버를 설치

npm install sequelize mysql2

 

 

2. Sequelize 기본 설정

sequelize를 설정하여 MySQL에 연결합니다.

 

const { Sequelize, DataTypes } = require('sequelize');

// MySQL 데이터베이스와 연결
const sequelize = new Sequelize('database_name', 'username', 'password', {
  host: 'localhost',
  dialect: 'mysql',
});

(async () => {
  try {
    // 연결 확인
    await sequelize.authenticate();
    console.log('Connection has been established successfully.');
  } catch (error) {
    console.error('Unable to connect to the database:', error);
  }
})();

 

 

  • Sequelize('database_name', 'username', 'password', {...}): 데이터베이스와 연결하기 위한 설정.
  • dialect: 'mysql': 사용하려는 데이터베이스가 MySQL임을 명시.
  • sequelize.authenticate(): 데이터베이스 연결이 성공했는지 확인하는 함수.

 

 

3. Sequelize 모델 정의 및 사용

모델은 데이터베이스의 테이블을 자바스크립트 객체로 나타내는 역할을 합니다.

 

모델 정의 (User 모델 예시)

const User = sequelize.define('User', {
  // 컬럼 정의
  id: {
    type: DataTypes.INTEGER,
    autoIncrement: true,
    primaryKey: true,
  },
  username: {
    type: DataTypes.STRING,
    allowNull: false,
  },
  email: {
    type: DataTypes.STRING,
    allowNull: false,
    unique: true,
  },
  password: {
    type: DataTypes.STRING,
    allowNull: false,
  },
}, {
  // 옵션
  tableName: 'users', // 테이블 이름
  timestamps: true,   // createdAt, updatedAt 자동 생성
});

// 데이터베이스에 모델을 동기화 (테이블 생성)
(async () => {
  await sequelize.sync();
})();

 

 

  • sequelize.define('ModelName', {...}): 모델을 정의하며, 첫 번째 인자로는 모델 이름, 두 번째 인자로는 모델의 속성을 정의합니다.
  • sequelize.sync(): 데이터베이스에 모델 구조를 반영하여 테이블을 생성합니다.
  • timestamps: true: createdAt과 updatedAt 컬럼을 자동으로 추가합니다.

 

 

4. 데이터 작업 예시

1) 데이터 삽입 (Create)

(async () => {
  const newUser = await User.create({
    username: 'john_doe',
    email: 'john@example.com',
    password: 'securepassword123'
  });

  console.log('New User Created:', newUser);
})();

User.create({...}): 테이블에 새로운 데이터를 삽입합니다.

 

2) 데이터 조회 (Read)

(async () => {
  const users = await User.findAll();
  console.log('All Users:', users);
})();

User.findAll(): users 테이블의 모든 데이터를 조회합니다.

 

(async () => {
  const user = await User.findOne({ where: { email: 'john@example.com' } });
  console.log('Found User:', user);
})();

User.findOne({ where: { ... } }): 특정 조건을 만족하는 첫 번째 데이터를 조회

 

3) 데이터 수정 (Update)

(async () => {
  const updatedUser = await User.update(
    { username: 'johnny_doe' },   // 수정할 데이터
    { where: { email: 'john@example.com' } }  // 조건
  );

  console.log('User Updated:', updatedUser);
})();

User.update({...}, { where: {...} }): 특정 조건을 만족하는 데이터를 수정

 

 

4) 데이터 삭제 (Delete)

(async () => {
  const result = await User.destroy({
    where: { email: 'john@example.com' }
  });

  console.log('User Deleted:', result);
})();

User.destroy({ where: {...} }): 특정 조건을 만족하는 데이터를 삭제

 

5. Associations (관계 설정)

Sequelize는 테이블 간의 관계를 정의할 수 있습니다. 예를 들어, UserPost 모델 간의 1

관계를 설정해 보겠습니다.

 

Post 모델 정의

const Post = sequelize.define('Post', {
  title: {
    type: DataTypes.STRING,
    allowNull: false,
  },
  content: {
    type: DataTypes.TEXT,
    allowNull: false,
  }
});

// 관계 정의 (1:N)
User.hasMany(Post);
Post.belongsTo(User);

// 테이블 동기화
(async () => {
  await sequelize.sync();
})();

 

  • User.hasMany(Post): 한 명의 사용자가 여러 개의 게시물을 가질 수 있는 관계.
  • Post.belongsTo(User): 게시물은 한 명의 사용자에게 속하는 관계.

 

 

 

# JWT 

인증에 사용되는 표준 토큰 기반의 인증 방식입니다. 서버와 클라이언트 간의 인증 정보를 안전하게 주고받기 사용

 

 

  • JWT는 사용자 인증에 많이 사용되며, 클라이언트와 서버 간의 무상태 인증을 구현하는 데 유용합니다.
  • jsonwebtoken 라이브러리를 사용하여 Node.js에서 JWT 토큰을 쉽게 생성하고 검증할 수 있습니다.
  • Express와 같은 프레임워크에서 JWT를 사용하여 인증 미들웨어를 간단하게 구현할 수 있습니다.

 

JWT란?

JWT는 세 부분으로 구성된 토큰입니다:

  1. Header (헤더): 토큰의 타입과 해싱 알고리즘을 명시합니다. 예: {"alg": "HS256", "typ": "JWT"}
  2. Payload (페이로드): 사용자의 정보(클레임)를 담고 있는 부분입니다. 예를 들어 사용자 ID, 권한 등을 저장할 수 있습니다.
  3. Signature (서명): 헤더와 페이로드를 조합하여 비밀키로 서명한 값입니다. 이 서명은 토큰이 변조되지 않았음을 검증하는 데 사용됩니다.

JWT는 기본적으로 서버에 세션을 저장하지 않고 클라이언트가 인증된 사용자임을 증명할 수 있는 방식을 제공합니다. 이를 통해 **무상태 인증(stateless authentication)**을 구현할 수 있습니다.

 

 

Node.js에서 JWT 사용 방법

jsonwebtoken 라이브러리를 사용

 

설치

npm install express jsonwebtoken bcryptjs

 

  • jsonwebtoken: JWT를 생성하고 검증하기 위해 사용.
  • bcryptjs: 비밀번호를 해싱하고 검증하기 위해 사용.

 

 

JWT 생성 및 발급 (토큰 발급)

const jwt = require('jsonwebtoken');

// 비밀키
const secretKey = 'your-secret-key';

// 사용자 정보 (페이로드)
const payload = {
  id: 1,
  username: 'john_doe',
  role: 'admin'
};

// 토큰 생성
const token = jwt.sign(payload, secretKey, { expiresIn: '1h' });

console.log('Generated Token:', token);

 

 

  • jwt.sign(payload, secretKey, options) 함수는 주어진 payload비밀키를 사용하여 JWT를 생성합니다.
  • expiresIn 옵션을 사용하여 토큰의 유효 기간을 설정할 수 있습니다 (예: '1h'는 1시간).

JWT 검증 및 디코딩 (토큰 검증)

클라이언트로부터 받은 JWT를 서버에서 검증

// 클라이언트로부터 받은 토큰
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';

// 토큰 검증 함수
jwt.verify(token, secretKey, (err, decoded) => {
  if (err) {
    // 검증 실패 처리
    console.log('Token is invalid or expired', err);
  } else {
    // 검증 성공, 디코딩된 정보 사용
    console.log('Decoded Payload:', decoded);
  }
});

 

 

 

  • jwt.verify(token, secretKey, callback) 함수는 클라이언트로부터 받은 JWT를 검증합니다.
  • 토큰이 유효하면 decoded 객체에 페이로드 정보가 반환되며, 유효하지 않거나 만료된 경우 에러가 발생합니다.

 

Express에서 JWT 인증 구현

Express.js에서 JWT를 사용한 간단한 인증 미들웨어

const express = require('express');
const jwt = require('jsonwebtoken');

const app = express();
const secretKey = 'your-secret-key';

// 미들웨어: JWT 인증
function authenticateToken(req, res, next) {
  const token = req.headers['authorization'];

  if (!token) return res.status(401).send('Access Denied');

  jwt.verify(token, secretKey, (err, decoded) => {
    if (err) return res.status(403).send('Invalid Token');
    
    req.user = decoded; // 토큰에 있는 유저 정보를 요청에 저장
    next();
  });
}

// 로그인 라우트 (토큰 발급)
app.post('/login', (req, res) => {
  // 사용자 정보 (일반적으로 DB에서 가져옴)
  const user = { id: 1, username: 'john_doe' };

  // JWT 생성
  const token = jwt.sign(user, secretKey, { expiresIn: '1h' });
  res.json({ token });
});

// 보호된 라우트 (JWT 필요)
app.get('/protected', authenticateToken, (req, res) => {
  res.send(`Hello ${req.user.username}, you have access to this route!`);
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

 

 

  • authenticateToken 미들웨어는 클라이언트가 요청에 포함한 JWT를 검증하고, 검증에 성공하면 다음 핸들러로 넘어가도록 처리합니다.
  • /login 엔드포인트는 로그인 후 JWT를 발급하는 역할을 합니다.
  • /protected 엔드포인트는 인증된 사용자만 접근할 수 있는 보호된 경로입니다.

 

예)

const express = require('express');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');

const app = express();
const secretKey = 'your-secret-key';

app.use(express.json()); // JSON 요청을 처리하기 위한 미들웨어

// 임시 사용자 데이터베이스
const users = [];

// 1. 회원가입 (이메일, 비밀번호 저장)
app.post('/register', async (req, res) => {
  const { email, password } = req.body;

  // 이메일 중복 확인
  const existingUser = users.find(user => user.email === email);
  if (existingUser) {
    return res.status(400).send('User already exists');
  }

  // 비밀번호 해싱
  const hashedPassword = await bcrypt.hash(password, 10);

  // 사용자 저장 (이메일과 해싱된 비밀번호)
  users.push({ email, password: hashedPassword });
  res.send('User registered successfully');
});

// 2. 로그인 (이메일, 비밀번호 검증 후 JWT 발급)
app.post('/login', async (req, res) => {
  const { email, password } = req.body;

  // 사용자 찾기
  const user = users.find(user => user.email === email);
  if (!user) {
    return res.status(400).send('User not found');
  }

  // 비밀번호 검증
  const isMatch = await bcrypt.compare(password, user.password);
  if (!isMatch) {
    return res.status(400).send('Invalid password');
  }

  // JWT 발급
  const token = jwt.sign({ email: user.email }, secretKey, { expiresIn: '1h' });
  res.json({ token });
});

// 3. JWT 인증 미들웨어
function authenticateToken(req, res, next) {
  const token = req.headers['authorization'];

  if (!token) return res.status(401).send('Access Denied');

  jwt.verify(token, secretKey, (err, decoded) => {
    if (err) return res.status(403).send('Invalid Token');

    req.user = decoded; // 유저 정보 저장
    next();
  });
}

// 4. 보호된 라우트 (JWT가 있어야 접근 가능)
app.get('/protected', authenticateToken, (req, res) => {
  res.send(`Hello ${req.user.email}, you have access to this route!`);
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

 

 

1. 회원가입 (/register)

  • 사용자가 이메일과 비밀번호를 입력하면, 비밀번호는 **bcryptjs**를 사용해 해싱하여 안전하게 저장됩니다.
  • 이미 존재하는 이메일로 회원가입하려고 하면 오류를 반환합니다.

2. 로그인 (/login)

  • 사용자가 로그인할 때, 저장된 사용자 데이터를 조회하고 비밀번호를 **bcrypt.compare()**를 사용해 해싱된 비밀번호와 입력된 비밀번호가 일치하는지 확인합니다.
  • 일치할 경우, JWT를 발급하고 사용자에게 반환합니다.

3. JWT 인증 미들웨어 (authenticateToken)

  • authorization 헤더에서 JWT를 가져와서, 유효한 토큰인지 **jwt.verify()**로 검증합니다.
  • 유효하지 않으면 403 오류를 반환하고, 유효하면 다음 라우트로 이동하게 합니다.

4. 보호된 라우트 (/protected)

  • 로그인 후 발급된 JWT를 헤더에 포함한 요청만 이 라우트에 접근할 수 있습니다.
  • JWT가 유효한 경우, 토큰에 저장된 이메일 정보를 사용해 보호된 정보를 응답합니다.

실행 흐름

  1. 회원가입: POST /register로 이메일과 비밀번호를 보내면 서버는 비밀번호를 해싱하고 저장합니다.
    • 예시 요청: { "email": "example@mail.com", "password": "password123" }
  2. 로그인: POST /login으로 이메일과 비밀번호를 보내면, 서버는 비밀번호를 확인하고 유효하다면 JWT를 발급합니다.
    • 예시 요청: { "email": "example@mail.com", "password": "password123" }
    • 응답 예시: { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6..." }
  3. 보호된 API 요청: GET /protected로 요청 시, Authorization 헤더에 발급받은 토큰을 넣어 요청합니다.
    • 예시 헤더: Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6...
    • 유효한 토큰일 경우, 성공적으로 보호된 정보를 반환합니다.

 

 

  • 사용자는 이메일과 비밀번호로 회원가입 및 로그인을 할 수 있습니다.
  • 로그인 시 JWT가 발급되고, 이 JWT를 사용해 인증된 사용자만 보호된 API에 접근할 수 있습니다.
  • bcryptjs는 비밀번호를 안전하게 해싱 및 검증하고, jsonwebtoken은 토큰을 생성 및 검증하는 데 사용됩니다.

 

반응형

'Node.js' 카테고리의 다른 글

[ Node.js ] AWS 배포  (0) 2025.03.13
[ Node ] 용어  (0) 2024.09.28
[ Node.js ] 05. 포스트맨 postman  (0) 2024.08.30
[ NoSQL ] MongoDB 개념과 설정  (0) 2024.06.10
[ Node.js ] 04. crud , HTTP method, MySQL 연동  (0) 2024.04.30