🛠️ JWT 인증 시스템 구현하기 – 완성까지 한번에!

개발 에러 해결 가이드 - FixLog 노트

프로젝트 소개 및 목표

JWT 인증 시스템 구현하기는 현대 웹 애플리케이션에서 필수적인 사용자 인증 및 권한 관리 시스템을 직접 구축해보는 프로젝트입니다. JWT(JSON Web Token)는 상태를 저장하지 않는(stateless) 인증 방식으로, 확장성이 뛰어나고 마이크로서비스 아키텍처에 적합합니다. 이 프로젝트를 통해 회원가입, 로그인, 토큰 발급 및 검증, 보호된 라우트 접근 제어 등 실제 서비스에 필요한 인증 시스템의 핵심 기능을 모두 구현할 수 있습니다. 완성된 시스템은 포트폴리오에 추가하거나 실제 프로젝트의 기반으로 활용할 수 있어 실무 역량을 크게 향상시킬 수 있습니다.

필요한 기술 스택

이 프로젝트를 성공적으로 완수하기 위해서는 다음과 같은 기술 스택이 필요합니다. 백엔드에는 Node.js와 Express.js 프레임워크를 사용하며, JWT 생성 및 검증을 위해 jsonwebtoken 라이브러리를 활용합니다. 비밀번호 암호화에는 bcrypt를 사용하고, 데이터베이스는 MongoDB와 Mongoose ODM을 채택합니다. 프론트엔드는 React.js로 구성하며, API 통신을 위해 axios를 사용합니다. 개발 도구로는 Postman(API 테스트), nodemon(자동 재시작), dotenv(환경변수 관리)를 활용합니다. 모든 기술은 오픈소스이며 무료로 사용 가능합니다.

프로젝트 셋업

프로젝트를 시작하기 위해 먼저 개발 환경을 구성합니다. Node.js(v16 이상)와 MongoDB를 설치한 후, 새로운 프로젝트 디렉토리를 생성합니다. 터미널에서 npm init -y 명령으로 package.json을 초기화하고, 필요한 패키지를 설치합니다:

npm install express mongoose jsonwebtoken bcrypt dotenv cors
npm install --save-dev nodemon

프로젝트 구조는 다음과 같이 구성합니다: server.js(진입점), models/(데이터 모델), routes/(라우트 핸들러), middleware/(인증 미들웨어), controllers/(비즈니스 로직). .env 파일을 생성하여 JWT_SECRET, MONGODB_URI, PORT 등의 환경변수를 설정합니다. package.json의 scripts에 "dev": "nodemon server.js"를 추가하여 개발 서버를 편리하게 실행할 수 있도록 합니다.

단계별 구현 과정

1단계: 데이터베이스 연결 및 사용자 모델 생성

MongoDB 연결을 설정하고 사용자 스키마를 정의합니다. models/User.js 파일을 생성합니다:

const mongoose = require('mongoose');
const bcrypt = require('bcrypt');

const userSchema = new mongoose.Schema({
  username: {
    type: String,
    required: true,
    unique: true,
    trim: true,
    minlength: 3
  },
  email: {
    type: String,
    required: true,
    unique: true,
    lowercase: true,
    match: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
  },
  password: {
    type: String,
    required: true,
    minlength: 6
  },
  role: {
    type: String,
    enum: ['user', 'admin'],
    default: 'user'
  },
  createdAt: {
    type: Date,
    default: Date.now
  }
});

// 비밀번호 저장 전 해싱
userSchema.pre('save', async function(next) {
  if (!this.isModified('password')) return next();
  this.password = await bcrypt.hash(this.password, 10);
  next();
});

// 비밀번호 비교 메서드
userSchema.methods.comparePassword = async function(candidatePassword) {
  return await bcrypt.compare(candidatePassword, this.password);
};

module.exports = mongoose.model('User', userSchema);

2단계: 인증 컨트롤러 구현

회원가입과 로그인 로직을 처리하는 컨트롤러를 작성합니다. controllers/authController.js 파일을 생성합니다:

const jwt = require('jsonwebtoken');
const User = require('../models/User');

// JWT 토큰 생성 함수
const generateToken = (userId, role) => {
  return jwt.sign(
    { id: userId, role: role },
    process.env.JWT_SECRET,
    { expiresIn: '24h' }
  );
};

// 회원가입
exports.register = async (req, res) => {
  try {
    const { username, email, password } = req.body;

    // 중복 체크
    const existingUser = await User.findOne({ $or: [{ email }, { username }] });
    if (existingUser) {
      return res.status(400).json({ message: '이미 존재하는 사용자입니다.' });
    }

    // 새 사용자 생성
    const user = new User({ username, email, password });
    await user.save();

    // 토큰 발급
    const token = generateToken(user._id, user.role);

    res.status(201).json({
      message: '회원가입이 완료되었습니다.',
      token,
      user: {
        id: user._id,
        username: user.username,
        email: user.email,
        role: user.role
      }
    });
  } catch (error) {
    res.status(500).json({ message: '서버 오류가 발생했습니다.', error: error.message });
  }
};

// 로그인
exports.login = async (req, res) => {
  try {
    const { email, password } = req.body;

    // 사용자 찾기
    const user = await User.findOne({ email });
    if (!user) {
      return res.status(401).json({ message: '이메일 또는 비밀번호가 잘못되었습니다.' });
    }

    // 비밀번호 확인
    const isPasswordValid = await user.comparePassword(password);
    if (!isPasswordValid) {
      return res.status(401).json({ message: '이메일 또는 비밀번호가 잘못되었습니다.' });
    }

    // 토큰 발급
    const token = generateToken(user._id, user.role);

    res.json({
      message: '로그인 성공',
      token,
      user: {
        id: user._id,
        username: user.username,
        email: user.email,
        role: user.role
      }
    });
  } catch (error) {
    res.status(500).json({ message: '서버 오류가 발생했습니다.', error: error.message });
  }
};

3단계: 인증 미들웨어 구현

JWT 토큰을 검증하는 미들웨어를 작성합니다. middleware/auth.js 파일을 생성합니다:

const jwt = require('jsonwebtoken');

// 토큰 검증 미들웨어
exports.verifyToken = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1]; // Bearer 토큰 추출

  if (!token) {
    return res.status(401).json({ message: '인증 토큰이 필요합니다.' });
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded; // 디코딩된 사용자 정보를 req에 저장
    next();
  } catch (error) {
    if (error.name === 'TokenExpiredError') {
      return res.status(401).json({ message: '토큰이 만료되었습니다.' });
    }
    return res.status(403).json({ message: '유효하지 않은 토큰입니다.' });
  }
};

// 관리자 권한 확인 미들웨어
exports.verifyAdmin = (req, res, next) => {
  if (req.user.role !== 'admin') {
    return res.status(403).json({ message: '관리자 권한이 필요합니다.' });
  }
  next();
};

4단계: 라우트 설정

API 엔드포인트를 정의합니다. routes/auth.js와 routes/protected.js 파일을 생성합니다:

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

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

module.exports = router;

// routes/protected.js
const express = require('express');
const router = express.Router();
const { verifyToken, verifyAdmin } = require('../middleware/auth');

// 보호된 라우트 (인증 필요)
router.get('/profile', verifyToken, (req, res) => {
  res.json({ message: '프로필 정보', user: req.user });
});

// 관리자 전용 라우트
router.get('/admin', verifyToken, verifyAdmin, (req, res) => {
  res.json({ message: '관리자 페이지에 오신 것을 환영합니다.' });
});

module.exports = router;

5단계: 서버 설정

모든 구성 요소를 통합하여 서버를 시작합니다. server.js 파일을 작성합니다:

require('dotenv').config();
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');

const authRoutes = require('./routes/auth');
const protectedRoutes = require('./routes/protected');

const app = express();

// 미들웨어
app.use(cors());
app.use(express.json());

// MongoDB 연결
mongoose.connect(process.env.MONGODB_URI, {
  useNewUrlParser: true,
  useUnifiedTopology: true
})
.then(() => console.log('MongoDB 연결 성공'))
.catch(err => console.error('MongoDB 연결 실패:', err));

// 라우트
app.use('/api/auth', authRoutes);
app.use('/api', protectedRoutes);

// 에러 핸들링
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ message: '서버 오류가 발생했습니다.' });
});

const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
  console.log(`서버가 포트 ${PORT}에서 실행 중입니다.`);
});

테스트 및 배포

구현이 완료되면 Postman을 사용하여 각 엔드포인트를 테스트합니다. POST /api/auth/register로 새 사용자를 생성하고, POST /api/auth/login으로 로그인하여 JWT 토큰을 받습니다. 받은 토큰을 Authorization 헤더에 ‘Bearer [토큰]’ 형식으로 포함하여 GET /api/profile에 요청을 보내 인증이 정상 작동하는지 확인합니다. 유닛 테스트는 Jest와 Supertest를 사용하여 작성할 수 있습니다. 배포는 Heroku, AWS EC2, 또는 Vercel(서버리스)을 활용할 수 있으며, 환경변수는 반드시 플랫폼의 설정 메뉴에서 안전하게 관리해야 합니다. 프로덕션 환경에서는 HTTPS를 필수로 사용하고, Rate Limiting(express-rate-limit)을 적용하여 보안을 강화합니다.

마무리 및 확장 아이디어

JWT 인증 시스템 구현하기 프로젝트를 완료하셨다면, 이제 실무에서 사용 가능한 인증 시스템의 핵심을 이해하게 되었습니다. 이 기반 위에 다양한 기능을 추가할 수 있습니다: Refresh Token을 도입하여 보안을 강화하고, 이메일 인증 기능을 추가하거나, OAuth 2.0(Google, GitHub 로그인)을 통합할 수 있습니다. Redis를 활용한 토큰 블랙리스트 관리, 2단계 인증(2FA), 비밀번호 재설정 기능 등도 훌륭한 확장 아이디어입니다. 이 프로젝트는 포트폴리오에 추가하기 좋으며, 실제 서비스 개발 시 즉시 활용할 수 있는 실용적인 자산이 됩니다.

📚 함께 읽으면 좋은 글

1

JWT 인증 시스템 구현하기 – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 23.
🎯 JWT 인증 시스템 구현하기

2

MongoDB와 Express.js로 블로그 만들기 – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 22.
🎯 MongoDB와 Express.js로 블로그 만들기

3

30분만에 만드는 Todo App 완성 가이드 – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 21.
🎯 30분만에 만드는 Todo App 완성 가이드

4

30분만에 만드는 Todo App 완성 가이드 – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 20.
🎯 30분만에 만드는 Todo App 완성 가이드

5

REST API 서버 구축 단계별 튜토리얼 – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 19.
🎯 REST API 서버 구축 단계별 튜토리얼

💡 위 글들을 통해 더 깊이 있는 정보를 얻어보세요!

📢 이 글이 도움되셨나요? 공유해주세요!

여러분의 공유 한 번이 더 많은 사람들에게 도움이 됩니다 ✨


📘 페이스북


🐦 트위터


✈️ 텔레그램

🔥 공유할 때마다 블로그 성장에 큰 힘이 됩니다! 감사합니다 🙏

💬 여러분의 소중한 의견을 들려주세요!

JWT 인증 시스템 구현하기 관련해서 궁금한 점이 더 있으시다면 언제든 물어보세요!

💡
유용한 정보 공유

궁금한 점 질문

🤝
경험담 나누기

👍
의견 표현하기

⭐ 모든 댓글은 24시간 내에 답변드리며, 여러분의 의견이 다른 독자들에게 큰 도움이 됩니다!
🎯 건설적인 의견과 경험 공유를 환영합니다 ✨

🔔 블로그 구독하고 최신 글을 받아보세요!

📚
다양한 주제
17개 카테고리

정기 업데이트
하루 3회 발행

🎯
실용적 정보
바로 적용 가능

💡
최신 트렌드
2025년 기준

🌟 프로젝트 아이디어부터 다양한 실생활 정보까지!
매일 새로운 유용한 콘텐츠를 만나보세요 ✨

📧 RSS 구독 | 🔖 북마크 추가 | 📱 모바일 앱 알림 설정
지금 구독하고 놓치는 정보 없이 업데이트 받아보세요!

📱 전체 버전 보기