JWT 토큰으로 Express 서버 인증 구현: 회원가입, 로그인, 글쓰기 완벽 가이드

1. JWT와 Express 소개

JWT (JSON Web Token)란?

JWT는 JSON 객체를 사용해 양쪽 사이에서 정보를 안전하게 전달하기 위한 컴팩트하고 자가 포함된 방식입니다. JWT는 주로 인증 및 정보 교환 목적으로 사용됩니다. JWT는 세 부분으로 구성되어 있습니다:
1. 헤더(Header)
2. 내용(Payload)
3. 서명(Signature)

헤더는 토큰의 타입과 해시 알고리즘 정보를 포함하고 있습니다. 내용은 토큰에 포함될 정보들이 들어가고, 서명은 토큰의 변조 여부를 확인할 수 있습니다.

예시 JWT 구조:


eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Express.js란?

Express.js는 웹 및 모바일 애플리케이션을 위한 강력하고 유연한 Node.js 웹 애플리케이션 프레임워크입니다. Express는 미들웨어 및 라우팅 기능을 제공하여 서버측 로직을 구축하는 데 매우 유용합니다.

전체적으로 Express는 다음과 같은 기능을 제공합니다:
– HTTP 요청 라우팅
– 미들웨어 지원
– 템플릿 엔진 지원
– REST API 구축 용이

Express.js를 사용하여 서버를 구축하는 간단한 예제는 다음과 같습니다:


const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.send('Hello World!');
});

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

JWT와 Express의 통합

JWT를 Express 애플리케이션과 통합하려면 몇 가지 단계를 거쳐야 합니다. 먼저 JWT 토큰을 생성하고 검증하는 라이브러리를 설치해야 합니다. 그런 다음, 인증 미들웨어를 만들어 JWT를 검증하면 됩니다.

라이브러리 설치:


npm install jsonwebtoken

JWT 토큰 생성 예제:


const jwt = require('jsonwebtoken');

const payload = {
  username: 'john.doe',
  role: 'user'
};

const secretKey = 'your-secret-key';
const token = jwt.sign(payload, secretKey, { expiresIn: '1h' });

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

JWT 검증 미들웨어 예제:


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

const secretKey = 'your-secret-key';

const authenticateJWT = (req, res, next) => {
  const token = req.header('Authorization');
  if (token) {
    jwt.verify(token, secretKey, (err, user) => {
      if (err) {
        return res.sendStatus(403);
      }
      req.user = user;
      next();
    });
  } else {
    res.sendStatus(401);
  }
};

app.get('/protected', authenticateJWT, (req, res) => {
  res.send('This is a protected route');
});

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

이제 JWT와 Express.js의 기본 개념과 간단한 예제를 통해 어떻게 통합하여 사용하는지 학습했습니다. 이제 이러한 개념을 바탕으로 회원가입, 로그인, 글쓰기 기능을 구현할 수 있습니다.


2. 프로젝트 설정 및 초기화

2.1 Node.js 설치 및 Express 프로젝트 생성

먼저, Node.js가 설치되어 있는지 확인하고 설치하는 과정입니다.

**Node.js 설치 (Windows)**:
1. [Node.js 공식 사이트](https://nodejs.org/)에서 LTS 버전을 다운로드합니다.
2. 다운로드한 설치 파일을 실행하고 설치를 완료합니다.

설치가 완료되었으면, 터미널을 열어 다음 명령어로 Node.js와 npm (Node Package Manager)이 올바르게 설치되었는지 확인합니다:


node -v
npm -v

**Express 프로젝트 생성**:
1. 원하는 디렉토리에 이동합니다.
2. `npm init`을 실행하여 프로젝트를 초기화합니다. 여러 질문이 나올 수 있는데, 기본값으로 진행해도 무방합니다.


mkdir my-express-app
cd my-express-app
npm init -y

3. Express.js를 설치합니다.


npm install express

4. 기본적인 Express 서버 설정을 파일로 만듭니다. 예를 들어, `index.js` 파일을 생성하고 다음과 같이 작성합니다:


const express = require('express');
const app = express();
const port = 3000;

app.get('/', (req, res) => {
  res.send('Hello World!');
});

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

5. 서버를 실행합니다:


node index.js

2.2 필요한 패키지 설치

프로젝트를 더 발전시키기 위해 필요한 여러 패키지를 설치해야 합니다. 예를 들어, JWT 인증을 위해 `jsonwebtoken`, 요청 데이터를 파싱하기 위해 `body-parser` 등입니다.


npm install jsonwebtoken body-parser

`index.js` 파일에서 이러한 패키지를 사용하도록 설정합니다:


const express = require('express');
const bodyParser = require('body-parser');
const jwt = require('jsonwebtoken');

const app = express();
const port = 3000;

// Body Parser Middleware
app.use(bodyParser.json());

// Example route to demonstrate JWT
app.post('/login', (req, res) => {
  const { username, password } = req.body;
  
  // For demonstration purposes, we use a static username and password
  if (username === 'user' && password === 'password') {
    const token = jwt.sign({ username }, 'your-secret-key', { expiresIn: '1h' });
    res.json({ token });
  } else {
    res.status(401).send('Invalid credentials');
  }
});

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

2.3 디렉토리 구조 설정

정돈된 디렉토리 구조는 프로젝트 확장 및 유지 보수를 용이하게 합니다. 기본적인 Express 프로젝트의 디렉토리 구조는 다음과 같이 설정할 수 있습니다:


my-express-app/
│
├── node_modules/
├── src/
│   ├── controllers/
│   │   └── authController.js
│   ├── models/
│   │   └── userModel.js
│   ├── routes/
│   │   └── authRoutes.js
│   ├── utils/
│   │   └── jwt.js
│   └── index.js
├── .gitignore
├── package.json
└── README.md

각 디렉토리 및 파일의 역할은 다음과 같습니다:
– **controllers**: 비즈니스 로직을 처리하는 코드
– **models**: 데이터베이스 모델 정의
– **routes**: API 라우팅 설정
– **utils**: 유틸리티 함수 및 설정
– **index.js**: 애플리케이션 엔트리 포인트

예를 들어, `authController.js` 파일은 다음과 같은 내용을 가질 수 있습니다:


const jwt = require('jsonwebtoken');

exports.login = (req, res) => {
  const { username, password } = req.body;
  
  if (username === 'user' && password === 'password') {
    const token = jwt.sign({ username }, 'your-secret-key', { expiresIn: '1h' });
    res.json({ token });
  } else {
    res.status(401).send('Invalid credentials');
  }
};

`authRoutes.js` 파일은 다음과 같이 설정됩니다:


const express = require('express');
const router = express.Router();
const authController = require('../controllers/authController');

router.post('/login', authController.login);

module.exports = router;

`index.js` 파일에서 라우트를 설정해줍니다:


const express = require('express');
const bodyParser = require('body-parser');
const authRoutes = require('./routes/authRoutes');

const app = express();
const port = 3000;

app.use(bodyParser.json());
app.use('/auth', authRoutes);

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

이제 이 설정을 통해 프로젝트를 깔끔하게 유지하며 확장할 수 있습니다.


3. JWT 인증 구현

3.1 JWT 토큰 생성 및 검증

JSON Web Token(JWT)을 사용하면 사용자 인증을 안전하게 수행할 수 있습니다. `jsonwebtoken` 패키지를 활용해 JWT 생성 및 검증을 구현합니다.

JWT 생성 함수:


const jwt = require('jsonwebtoken');

const generateToken = (user) => {
  const payload = { username: user.username };
  return jwt.sign(payload, 'your-secret-key', { expiresIn: '1h' });
};

JWT 검증 미들웨어:


const jwt = require('jsonwebtoken');

const authenticateJWT = (req, res, next) => {
  const token = req.header('Authorization');
  if (token) {
    jwt.verify(token, 'your-secret-key', (err, user) => {
      if (err) {
        return res.sendStatus(403);
      }
      req.user = user;
      next();
    });
  } else {
    res.sendStatus(401);
  }
};

module.exports = authenticateJWT;

3.2 사용자 모델 정의

사용자 데이터를 관리할 모델을 정의합니다. 이 예제에서는 메모리 내에서 사용자 데이터를 관리하지만, 실제 어플리케이션에서는 데이터베이스를 사용해야 합니다.

간단한 사용자 모델 (`userModel.js`) 예제:


const users = [];

const addUser = (username, password) => {
  const user = { username, password };
  users.push(user);
};

const findUser = (username) => {
  return users.find(user => user.username === username);
};

module.exports = { addUser, findUser };

3.3 Register 및 Login API 구현

사용자가 회원가입 및 로그인을 할 수 있는 API를 구현합니다.

**Register API**:


const express = require('express');
const router = express.Router();
const { addUser, findUser } = require('../models/userModel');

router.post('/register', (req, res) => {
  const { username, password } = req.body;
  if (findUser(username)) {
    return res.status(409).send('User already exists');
  }
  addUser(username, password);
  res.status(201).send('User registered successfully');
});

module.exports = router;

**Login API**:


const express = require('express');
const router = express.Router();
const jwt = require('jsonwebtoken');
const { findUser } = require('../models/userModel');
const generateToken = require('../utils/jwt');

router.post('/login', (req, res) => {
  const { username, password } = req.body;
  const user = findUser(username);
  
  if (!user || user.password !== password) {
    return res.status(401).send('Invalid credentials');
  }
  
  const token = generateToken(user);
  res.json({ token });
});

module.exports = router;

**Main Application (`index.js`)**:


const express = require('express');
const bodyParser = require('body-parser');
const registerRoutes = require('./routes/registerRoutes');
const loginRoutes = require('./routes/loginRoutes');
const authenticateJWT = require('./utils/authenticateJWT');

const app = express();
const port = 3000;

app.use(bodyParser.json());
app.use('/auth', registerRoutes);
app.use('/auth', loginRoutes);

app.get('/protected', authenticateJWT, (req, res) => {
  res.send('This is a protected route');
});

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

이제 사용자는 `/auth/register` 경로를 통해 회원가입을 할 수 있고, `/auth/login` 경로를 통해 로그인을 할 수 있습니다. JWT가 성공적으로 생성되면 보호된 경로에서 이 토큰을 사용해 인증할 수 있습니다.


4. 기능별 API 구현

4.1 회원가입 API

사용자가 회원가입을 할 수 있는 API를 구현합니다. 이 과정에서 새로운 사용자를 `userModel.js`에 추가합니다.

회원가입 라우터 (`registerRoutes.js`):


const express = require('express');
const router = express.Router();
const { addUser, findUser } = require('../models/userModel');

router.post('/register', (req, res) => {
  const { username, password } = req.body;
  if (findUser(username)) {
    return res.status(409).send('User already exists');
  }
  addUser(username, password);
  res.status(201).send('User registered successfully');
});

module.exports = router;

이 파일을 메인 애플리케이션 파일에 추가합니다 (`index.js`):


const express = require('express');
const bodyParser = require('body-parser');
const registerRoutes = require('./routes/registerRoutes');
const loginRoutes = require('./routes/loginRoutes');
const postRoutes = require('./routes/postRoutes');
const authenticateJWT = require('./utils/authenticateJWT');

const app = express();
const port = 3000;

app.use(bodyParser.json());
app.use('/auth', registerRoutes);
app.use('/auth', loginRoutes);
app.use('/posts', authenticateJWT, postRoutes);

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

4.2 로그인 API

사용자가 로그인 할 수 있도록 하는 API를 구현합니다.

로그인 라우터 (`loginRoutes.js`):


const express = require('express');
const router = express.Router();
const jwt = require('jsonwebtoken');
const { findUser } = require('../models/userModel');
const generateToken = require('../utils/jwt');

router.post('/login', (req, res) => {
  const { username, password } = req.body;
  const user = findUser(username);
  
  if (!user || user.password !== password) {
    return res.status(401).send('Invalid credentials');
  }
  
  const token = generateToken(user);
  res.json({ token });
});

module.exports = router;

로그인 API도 같은 방식으로 메인 애플리케이션 파일에 추가되어야 합니다 (이미 추가된 코드가 있음).

4.3 글쓰기 API

사용자가 인증을 거쳐 글을 작성할 수 있는 API를 구현합니다. 글 데이터는 메모리 내에서 관리될 수 있습니다.

글쓰기 모델 (`postModel.js`):


const posts = [];

const addPost = (username, content) => {
  const post = { username, content, timestamp: new Date() };
  posts.push(post);
};

const getPosts = () => {
  return posts;
};

module.exports = { addPost, getPosts };

글쓰기 라우터 (`postRoutes.js`):


const express = require('express');
const router = express.Router();
const { addPost, getPosts } = require('../models/postModel');

router.post('/write', (req, res) => {
  const { content } = req.body;
  const username = req.user.username;

  addPost(username, content);
  res.status(201).send('Post created successfully');
});

router.get('/', (req, res) => {
  res.json(getPosts());
});

module.exports = router;

메인 애플리케이션 파일에서 글쓰기 라우터를 추가합니다 (`index.js`):


const express = require('express');
const bodyParser = require('body-parser');
const registerRoutes = require('./routes/registerRoutes');
const loginRoutes = require('./routes/loginRoutes');
const postRoutes = require('./routes/postRoutes');
const authenticateJWT = require('./utils/authenticateJWT');

const app = express();
const port = 3000;

app.use(bodyParser.json());
app.use('/auth', registerRoutes);
app.use('/auth', loginRoutes);
app.use('/posts', authenticateJWT, postRoutes);

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

이제 사용자는 `/auth/register` 경로로 회원가입을 할 수 있고, `/auth/login` 경로로 로그인을 할 수 있으며, 로그인 후에는 `/posts/write` 경로로 글을 작성할 수 있습니다. 작성된 글은 `/posts` 경로에서 확인할 수 있습니다.


5. 테스트 및 배포

5.1 Postman으로 API 테스트

Postman은 API를 테스트할 수 있는 강력한 도구입니다. 아래의 단계별 지시에 따라 API 테스트를 진행할 수 있습니다.

1. **Postman 설치**: [Postman 공식 웹사이트](https://www.postman.com/)에서 Postman을 다운로드하고 설치합니다.
2. **회원가입 API 테스트**:
– **Request Type**: POST
– **URL**: `http://localhost:3000/auth/register`
– **Body**:
“`json
{
“username”: “testuser”,
“password”: “password123”
}
“`
– **Send**를 클릭하여 요청을 보냅니다. 성공적으로 회원가입이 되었다면 `201` 상태 코드와 함께 `User registered successfully` 메시지를 확인할 수 있습니다.

3. **로그인 API 테스트**:
– **Request Type**: POST
– **URL**: `http://localhost:3000/auth/login`
– **Body**:
“`json
{
“username”: “testuser”,
“password”: “password123”
}
“`
– **Send**를 클릭하여 요청을 보냅니다. 성공적으로 로그인되었다면 JWT 토큰이 포함된 JSON 응답이 반환됩니다.
“`json
{
“token”: “YOUR_JWT_TOKEN”
}
“`

4. **글쓰기 API 테스트**:
– **Request Type**: POST
– **URL**: `http://localhost:3000/posts/write`
– **Headers**:
– Key: `Authorization`
– Value: `Bearer YOUR_JWT_TOKEN` (로그인 시 반환된 JWT 토큰)
– **Body**:
“`json
{
“content”: “This is a test post”
}
“`
– **Send**를 클릭하여 요청을 보냅니다. 성공적으로 글이 작성되었다면 `201` 상태 코드와 함께 `Post created successfully` 메시지를 확인할 수 있습니다.

5. **글 목록 조회 API 테스트**:
– **Request Type**: GET
– **URL**: `http://localhost:3000/posts`
– **Headers**:
– Key: `Authorization`
– Value: `Bearer YOUR_JWT_TOKEN`
– **Send**를 클릭하여 요청을 보냅니다. 작성된 글 목록이 JSON 형태로 반환됩니다.
“`json
[
{
“username”: “testuser”,
“content”: “This is a test post”,
“timestamp”: “2023-10-05T13:32:00.000Z”
}
]
“`

5.2 서버 배포 전략

서버를 배포하기 위해 여러 전략이 있습니다. 여기서는 AWS EC2를 이용한 배포 방법을 설명합니다.

1. **AWS EC2 인스턴스 생성**:
– AWS 콘솔에 로그인하고, EC2 대시보드로 이동하여 새로운 인스턴스를 생성합니다.
– Ubuntu Server(20.04 LTS)와 같은 운영 체제를 선택합니다.
– 인스턴스 유형을 선택하고 인스턴스를 시작합니다.

2. **EC2 인스턴스에 코드 배포**:
– 생성된 인스턴스에 SSH로 접속합니다.
“`sh
ssh -i “your-key.pem” ubuntu@ec2-your-public-dns.compute.amazonaws.com
“`

3. **Node.js 및 Git 설치**:
– Node.js를 설치합니다.
“`sh
sudo apt update
sudo apt install -y nodejs npm
“`
– Git을 설치합니다.
“`sh
sudo apt install -y git
“`

4. **프로젝트 클론 및 설치**:
– Github 리포지토리에서 프로젝트를 클론합니다.
“`sh
git clone https://github.com/your-repo/your-project.git
cd your-project
“`
– 프로젝트의 dependencies를 설치합니다.
“`sh
npm install
“`

5. **서버 실행**:
– 환경 변수를 설정하고 서버를 실행합니다.
“`sh
export PORT=3000
node index.js
“`

6. **Nginx 설정** (옵션):
– Nginx를 설치하여 Reverse Proxy로 사용할 수 있습니다.
“`sh
sudo apt install -y nginx
“`
– Nginx 설정 파일을 편집합니다.
“`sh
sudo nano /etc/nginx/sites-available/default
“`
– 다음 내용을 추가합니다:
“`nginx
server {
listen 80;
server_name your-domain.com;

location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection ‘upgrade’;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
“`
– Nginx를 재시작합니다.
“`sh
sudo systemctl restart nginx
“`

이제 서버가 배포되었으며, 해당 도메인 또는 IP 주소로 접근할 수 있습니다.


Leave a Comment