백엔드 개발에서 REST API를 구현할 때, 보안은 매우 중요한 요소입니다. 다양한 인증 방식이 존재하지만, 이번 글에서는 가장 널리 사용되는 JWT(JSON Web Token)를 이용한 인증 방식을 단계별로 구현해 보겠습니다. 이 튜토리얼을 통해 REST API의 보안을 강화하는 방법을 이해하고 직접 구현해보세요.

1. JWT란 무엇인가?

JWT는 JSON 형식의 정보를 안전하게 전송하기 위한 간단하고 독립적인 방법입니다. 인증 정보를 포함하여 사용자의 ID, 권한 등의 정보를 담을 수 있으며, 서버와 클라이언트 간의 상태를 유지하는 데 유용합니다. JWT는 세 부분으로 구성되어 있습니다.

  • Header: 토큰의 유형과 서명 알고리즘 정보
  • Payload: 실제 전송할 데이터
  • Signature: 데이터의 무결성을 확인하기 위한 서명

2. 프로젝트 설정

우선, Node.js와 Express.js를 이용하여 기본 프로젝트를 설정해봅시다. 아래 명령어를 통해 새로운 프로젝트를 생성합니다.

mkdir jwt-auth-example
cd jwt-auth-example
npm init -y
npm install express jsonwebtoken dotenv bcryptjs

3. 환경 변수 설정

.env 파일을 만들어 비밀 키를 저장합니다.
JWT_SECRET=your_secret_key

4. 기본 서버 설정

server.js 파일을 생성하고, Express 서버를 설정합니다.
const express = require('express');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
require('dotenv').config();

const app = express();
app.use(express.json());

const users = []; // 사용자 정보를 저장할 배열

5. 사용자 등록 및 로그인

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

// 사용자 등록
app.post('/register', async (req, res) => {
    const { username, password } = req.body;
    const hashedPassword = await bcrypt.hash(password, 10);
    
    users.push({ username, password: hashedPassword });
    res.status(201).send('User registered');
});

// 사용자 로그인
app.post('/login', async (req, res) => {
    const { username, password } = req.body;
    const user = users.find(user => user.username === username);
    
    if (!user || !(await bcrypt.compare(password, user.password))) {
        return res.status(401).send('Invalid credentials');
    }

    const token = jwt.sign({ username: user.username }, process.env.JWT_SECRET, { expiresIn: '1h' });
    res.json({ token });
});

6. 인증이 필요한 API

이제 인증이 필요한 API를 구현해 보겠습니다. JWT를 검증하는 미들웨어를 작성하여, 보호할 API를 설정합니다.

// JWT 검증 미들웨어
const authenticateJWT = (req, res, next) => {
    const token = req.headers['authorization']?.split(' ')[1];
    
    if (!token) {
        return res.sendStatus(403);
    }
    
    jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
        if (err) {
            return res.sendStatus(403);
        }
        req.user = user;
        next();
    });
};

// 인증이 필요한 API
app.get('/protected', authenticateJWT, (req, res) => {
    res.send(Hello ${req.user.username});
});

7. 서버 실행

마지막으로 서버를 실행해 보겠습니다.

const PORT = process.env.PORT || 3000;

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

이제 우리는 기본적인 JWT 인증이 포함된 REST API를 구현했습니다. /register와 /login 엔드포인트를 통해 사용자를 등록하고 인증할 수 있으며, /protected 엔드포인트에 접근하려면 JWT를 포함해야 합니다.

8. 테스트

Postman과 같은 API 테스트 도구를 사용하여 아래와 같이 요청을 보낼 수 있습니다.

  1. 사용자 등록: POST http://localhost:3000/register에 JSON 형태로 사용자 정보 전송
  2.    {
           "username": "testuser",
           "password": "testpassword"
       }
  3. 로그인: POST http://localhost:3000/login에 동일한 사용자 정보 전송
  4.    {
           "username": "testuser",
           "password": "testpassword"
       }

    성공적으로 로그인하면 JWT 토큰이 반환됩니다.

  5. 보호된 리소스 접근: GET http://localhost:3000/protected에 Authorization 헤더에 토큰을 포함하여 요청
   Authorization: Bearer <your_jwt_token>

마무리

이제 JWT를 사용한 REST API 인증 방식을 구현했습니다. 이 방식은 간단하면서도 강력한 보안 기능을 제공하여, 클라이언트와 서버 간의 안전한 데이터 전송을 가능하게 합니다. 다양한 프로젝트에서 이 방식을 활용하여 보안을 강화해보세요!