React와 Node.js를 사용한 웹 애플리케이션 개발

React와 Node.js를 사용한 웹 애플리케이션(게시판) 개발 강좌(13)

atomicdev 2024. 9. 19. 11:35
728x90

JWT를 이용한 사용자 인증 구현: 회원가입 및 로그인 기능

이번 강의에서는 **JWT(Json Web Token)**를 사용해 사용자 인증 및 권한 부여를 구현하는 방법을 다룹니다. JWT는 웹 애플리케이션에서 사용자 인증에 널리 사용되는 방식으로, 사용자가 로그인할 때 서버에서 토큰을 발급하고, 클라이언트는 이를 저장해 이후의 요청에서 인증을 유지합니다.

JWT를 이용한 사용자 인증 구현


1. JWT란?

**JWT(Json Web Token)**는 JSON 형식의 데이터를 기반으로 사용자 인증 및 권한 부여를 위한 표준입니다. JWT는 세 가지 부분으로 나뉩니다:

  1. Header: 토큰의 타입(JWT)과 해싱 알고리즘(예: HS256) 정보.
  2. Payload: 사용자 관련 정보(예: 사용자 ID, 권한 등)가 포함됨.
  3. Signature: Header와 Payload를 해싱한 값으로, 토큰의 무결성을 검증.

JWT는 주로 Bearer 토큰 방식으로 HTTP 헤더에 포함되어 클라이언트에서 서버로 전달됩니다.


2. JWT 설치 및 설정

2.1 필요한 라이브러리 설치

JWT를 사용하기 위해 jsonwebtokenbcryptjs(비밀번호 해싱을 위한 라이브러리)를 설치합니다.

npm install jsonwebtoken bcryptjs
 

3. 회원가입 및 로그인 기능 구현

3.1 MySQL에 사용자 테이블 생성

MySQL 데이터베이스에서 사용자 정보를 저장할 users 테이블을 생성합니다.

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(100),
    password VARCHAR(255)
);
 

3.2 회원가입 기능 구현

회원가입 시 사용자가 입력한 비밀번호는 bcryptjs를 사용해 해싱된 상태로 저장합니다.

// server.js
const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const db = require('./db');
const app = express();

app.use(express.json());

// JWT 시크릿 키
const JWT_SECRET = 'your_jwt_secret_key';

// 회원가입 API
app.post('/register', (req, res) => {
  const { username, password } = req.body;
  
  // 비밀번호 해싱
  const hashedPassword = bcrypt.hashSync(password, 8);

  // MySQL에 사용자 정보 저장
  const query = 'INSERT INTO users (username, password) VALUES (?, ?)';
  db.query(query, [username, hashedPassword], (err, result) => {
    if (err) {
      return res.status(500).send('Error registering user');
    }
    res.status(201).send('User registered successfully');
  });
});

3.3 로그인 기능 구현

로그인 시 입력한 비밀번호를 bcrypt를 사용해 해싱된 비밀번호와 비교한 후, 인증에 성공하면 JWT를 생성하여 클라이언트에 전달합니다.

// 로그인 API
app.post('/login', (req, res) => {
  const { username, password } = req.body;

  // 사용자 확인
  const query = 'SELECT * FROM users WHERE username = ?';
  db.query(query, [username], (err, results) => {
    if (err || results.length === 0) {
      return res.status(404).send('User not found');
    }

    const user = results[0];
    const passwordIsValid = bcrypt.compareSync(password, user.password);
    
    if (!passwordIsValid) {
      return res.status(401).send('Invalid password');
    }

    // JWT 생성
    const token = jwt.sign({ id: user.id }, JWT_SECRET, {
      expiresIn: '1h' // 토큰 만료 시간 설정
    });

    res.json({ auth: true, token });
  });
});

3.4 인증 미들웨어

JWT를 검증하는 미들웨어를 만들어, 보호된 API에서 인증된 사용자만 접근할 수 있도록 설정합니다.

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

  if (!token) {
    return res.status(403).send('No token provided');
  }

  jwt.verify(token.split(' ')[1], JWT_SECRET, (err, decoded) => {
    if (err) {
      return res.status(500).send('Failed to authenticate token');
    }
    req.userId = decoded.id;
    next();
  });
}

// 보호된 라우트 예시
app.get('/protected', verifyToken, (req, res) => {
  res.send(`Hello User ${req.userId}, you are authenticated`);
});

4. 프론트엔드에서 JWT 사용

4.1 로그인 후 토큰 저장

로그인 후 JWT를 받아 localStorage 또는 sessionStorage에 저장할 수 있습니다. 이 토큰은 이후 API 요청 시 인증에 사용됩니다.

// src/components/Login.js
import React, { useState } from 'react';
import axios from 'axios';

function Login() {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');

  const handleLogin = (e) => {
    e.preventDefault();
    
    axios.post('/login', { username, password })
      .then((response) => {
        localStorage.setItem('token', response.data.token);
        alert('Logged in successfully');
      })
      .catch((error) => {
        console.error('Error logging in:', error);
      });
  };

  return (
    <form onSubmit={handleLogin}>
      <h2>로그인</h2>
      <div>
        <label>아이디</label>
        <input
          type="text"
          value={username}
          onChange={(e) => setUsername(e.target.value)}
        />
      </div>
      <div>
        <label>비밀번호</label>
        <input
          type="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
        />
      </div>
      <button type="submit">로그인</button>
    </form>
  );
}

export default Login;

5. 보호된 API에 접근

프론트엔드에서 보호된 API에 접근할 때, 저장된 JWT를 Authorization 헤더에 추가하여 요청을 보냅니다.

// 보호된 API 호출 예시
axios.get('/protected', {
  headers: {
    'Authorization': `Bearer ${localStorage.getItem('token')}`
  }
})
.then((response) => {
  console.log('Protected data:', response.data);
})
.catch((error) => {
  console.error('Error accessing protected API:', error);
});

6. JWT 기반 인증 흐름 요약

  1. 회원가입: 사용자가 회원가입 시 비밀번호는 해싱된 상태로 MySQL에 저장됩니다.
  2. 로그인: 사용자가 로그인하면 JWT가 생성되어 클라이언트에 전달됩니다.
  3. JWT 저장: 클라이언트는 JWT를 localStorage 또는 sessionStorage에 저장하여 이후 요청에서 사용합니다.
  4. 인증 미들웨어: 서버에서는 보호된 API에 접근할 때 JWT를 검증하여 사용자의 권한을 확인합니다.

결론

이번 강의에서는 JWT를 사용해 사용자 인증 및 권한 부여를 구현하는 방법을 배웠습니다. 회원가입, 로그인, JWT 생성 및 검증, 그리고 보호된 API 접근까지의 흐름을 통해, 웹 애플리케이션에서 안전한 인증 시스템을 구축할 수 있습니다.

728x90