개발천재

[Express] JWT 토큰으로 안전한 인증 시스템 구축하기 본문

개발 준비/nodeJS&Express

[Express] JWT 토큰으로 안전한 인증 시스템 구축하기

세리블리 2025. 4. 29. 15:23
728x90
반응형

JWT 이해하기

JWT는 사용자 인증 및 정보 전송을 안전하게 처리하기 위한 표준으로, JSON 객체 형식으로 구성된 토큰이다. 주로 인증과 권한 부여에 사용되며, 클라이언트와 서버 간의 신뢰할 수 있는 정보를 안전하게 교환할 수 있도록 해준다.

 

JWT는 3부분으로 구성된다.

  • 헤더(Header): 토큰의 타입과 서명 알고리즘을 지정한다.
  • 페이로드(Payload): 사용자 정보를 포함하는 부분이다.
  • 서명(Signature): JWT의 무결성을 확인할 수 있는 부분이다.

이 세부분은 .(점)으로 구분되어 하나의 문자열로 합쳐진다.

header.payload.signature

 

 


 

 

JWT 설치하기

먼저, JWT를 사용하기 위해 필요한 라이브러리를 설치해야 한다. jsonwebtoken 패키지를 설치한다.

npm install jsonwebtoken

 

 

 

JWT를 생성하고 검증하는 방법

JWT를 사용하려면, 서버에서 토큰을 생성하고, 이를 클라이언트에게 반환하여 클라이언트가 요청을 보낼 때마다 토큰을 검증해야 한다.

1) JWT 생성하기 (로그인 시 토큰 발급)
로그인한 사용자가 로그인 정보(예: 사용자 이름, 비밀번호 등)를 통해 JWT를 생성하고, 클라이언트에게 반환한다. 클라이언트는 이 토큰을 저장하고, 이후 요청 시마다 이 토큰을 헤더에 포함시켜 서버에 요청을 보낸다.

예시: 로그인 후 JWT 발급하기

// authController.js
const jwt = require('jsonwebtoken');

// JWT 비밀 키 (환경변수로 관리하는 것이 좋습니다)
const secretKey = 'your-secret-key'; 

// 로그인 후 토큰 발급
const login = (req, res) => {
  const { username, password } = req.body;

  // 이 예시에서는 간단하게 "admin"이라는 사용자명과 "password"라는 비밀번호를 체크합니다.
  // 실제로는 DB에서 사용자 정보를 확인해야 합니다.
  if (username === 'admin' && password === 'password') {
    // 사용자 정보를 기반으로 JWT 생성 (여기서는 간단한 예시로 u_id만 포함)
    const user = { u_id: 1, username: 'admin' };

    const token = jwt.sign(user, secretKey, { expiresIn: '1h' }); // 1시간 유효
    return res.json({ token });
  } else {
    return res.status(401).json({ message: '잘못된 사용자명 또는 비밀번호입니다.' });
  }
};

module.exports = { login };


위 코드에서는 사용자가 admin으로 로그인할 경우 u_id와 username을 포함한 JWT 토큰을 생성하고 반환한다. 이 토큰은 클라이언트가 저장한 후, 후속 요청에 사용된다.



2) JWT 인증 미들웨어 (요청 시 토큰 검증)
서버에서는 클라이언트가 보낸 JWT를 검증하여 인증된 사용자만 접근할 수 있도록 한다. 클라이언트는 요청 헤더의 Authorization에 Bearer <token> 형식으로 토큰을 보낸다.

예시: JWT 인증 미들웨어

// authenticateToken.js
const jwt = require('jsonwebtoken');
const secretKey = 'your-secret-key';  // 비밀 키 (환경 변수로 관리하는 것이 좋음)

// JWT 인증 미들웨어
const authenticateToken = (req, res, next) => {
  const token = req.header('Authorization')?.split(' ')[1];  // "Bearer <token>" 형태로 토큰 추출

  if (!token) {
    return res.status(401).json({ message: '토큰이 없습니다. 인증되지 않은 사용자입니다.' });
  }

  // JWT 토큰 검증
  jwt.verify(token, secretKey, (err, user) => {
    if (err) {
      return res.status(403).json({ message: '유효하지 않은 토큰입니다.' });
    }
    req.user = user;  // 사용자 정보를 req.user에 저장
    next();
  });
};

module.exports = authenticateToken;


위 미들웨어는 Authorization 헤더에서 JWT를 추출하고 이를 검증한다. 검증이 성공하면 req.user에 사용자 정보를 추가하여 후속 처리에서 사용할 수 있게 한다.

 

 

3) 미들웨어를 라우터에 적용
이제 authenticateToken 미들웨어를 사용하여 보호된 라우터에 인증을 적용한다.

예시: JWT 인증을 필요한 라우터에 적용

// app.js (메인 서버 파일)
const express = require('express');
const jwt = require('jsonwebtoken');
const authenticateToken = require('./middleware/authenticateToken');
const { login } = require('./controllers/authController');  // 로그인 함수
const { createServiceRequest } = require('./controllers/requestController');  // 비자 서비스 요청

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

// 로그인 라우터 (토큰 발급)
app.post('/login', login);

// 서비스 요청 라우터 (인증된 사용자만 접근 가능)
app.post('/api/service-request', authenticateToken, createServiceRequest);

// 서버 시작
app.listen(3000, () => {
  console.log('서버가 3000번 포트에서 실행 중입니다.');
});

 

 

위와 같이 authenticateToken 미들웨어를 /api/service-request 라우터에 적용하면, 로그인된 사용자만 해당 라우터에 접근할 수 있다.

 

 

4) 서비스 요청 처리 (로그인된 사용자의 u_id 사용)

createServiceRequest 함수에서는 req.user에서 로그인된 사용자의 u_id를 가져와 데이터베이스에 저장할 수 있다.

// requestController.js
const db = require('../config/db');

// 비자 서비스 견적 요청 저장
const createServiceRequest = async (req, res) => {
  const { country, city, departureDate, serviceType, servicePurpose, consultationPreference, selectedCompany, createdAt } = req.body;

  // 로그인된 사용자의 ID 가져오기
  const { u_id } = req.user;  // JWT 미들웨어에서 가져온 로그인된 사용자 정보

  try {
    // 비자 서비스 견적 요청을 MySQL에 저장
    const result = await db.query(
      `INSERT INTO request (ref_u_id, service_type, contents, status, created_at, updated_at) 
       VALUES (?, ?, ?, ?, ?, ?)`,
      [
        u_id, // 로그인된 사용자의 u_id 사용
        10, // 서비스 유형
        JSON.stringify({ country, city, departureDate, serviceType, servicePurpose, consultationPreference, selectedCompany, createdAt }), // JSON 형태로 저장
        0, // 상태 0: 견적 요청
        new Date(),
        new Date(),
      ]
    );

    res.status(201).json({ message: '서비스 견적 요청이 저장되었습니다.' });
  } catch (error) {
    console.error(error.message);
    res.status(500).json({ message: '서버 오류가 발생했습니다.', error: error.message });
  }
};

module.exports = { createServiceRequest };

 

 

 

클라이언트에서 JWT 토큰을 이용한 요청 보내기

클라이언트에서는 로그인 후 받은 JWT 토큰을 Authorization 헤더에 포함시켜 서버로 요청을 보낸다.

예시: 클라이언트에서 요청 보내기 (React 예시)

import axios from 'axios';

const sendServiceRequest = async (serviceData) => {
  const token = localStorage.getItem('token');  // 로컬 스토리지에서 토큰 가져오기

  try {
    const response = await axios.post(
      'http://localhost:3000/api/service-request', 
      { service: serviceData },
      { headers: { Authorization: `Bearer ${token}` } }  // Authorization 헤더에 토큰 포함
    );
    console.log(response.data);
  } catch (error) {
    console.error('Error sending service request:', error.response.data);
  }
};

 

 

로그인 후 클라이언트에서 토큰 저장

로그인 후 클라이언트에서 받은 토큰은 보통 localStorage나 sessionStorage에 저장하여 후속 요청에서 사용한다.

const handleLogin = async (username, password) => {
  try {
    const response = await axios.post('http://localhost:3000/login', { username, password });
    const { token } = response.data;
    localStorage.setItem('token', token);  // 토큰을 로컬 스토리지에 저장
    console.log('로그인 성공');
  } catch (error) {
    console.error('로그인 실패:', error.response.data);
  }
};



요약정리

아래와 같은 방식으로 JWT를 이용한 인증 시스템을 구현할 수 있다.

  • JWT 생성: 사용자 로그인 시 JWT를 생성하고 클라이언트에 반환한다.
  • JWT 검증: 서버에서 요청이 들어올 때마다 토큰을 검증하고, 유효한 사용자만 요청을 처리한다.
  • 클라이언트 요청: 클라이언트는 JWT를 Authorization 헤더에 담아 요청을 보낸다.
반응형