이전 글에서는 백엔드 API와 프론트엔드를 연결하고, 데이터를 주고받는 기본적인 풀스택 앱 구현을 살펴봤습니다. 이번 글에서는 사용자 인증과 권한 관리를 구현하여 앱의 보안을 강화하는 방법을 다룹니다. 이는 실무에서 풀스택 앱 개발 시 필수적인 과정 중 하나입니다.
1. JWT를 활용한 사용자 인증
JWT(Json Web Token)는 사용자의 로그인 세션을 관리하고 인증하는 데 널리 사용되는 방식입니다. JWT는 서버에서 생성된 토큰을 클라이언트에게 전달하고, 클라이언트는 이를 저장한 후 요청 시마다 서버에 전달합니다. 서버는 이 토큰을 확인하여 사용자를 인증합니다.
1.1 백엔드에서 JWT 생성
우선, 사용자가 로그인할 때 백엔드에서 JWT를 생성하여 반환하는 기능을 구현해보겠습니다.
// app.js (JWT 생성)
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
const port = 3000;
// 사용자 인증을 위한 예시 데이터
const users = [{ id: 1, username: 'user1', password: 'password1' }];
// 로그인 API
app.post('/api/login', (req, res) => {
const { username, password } = req.body;
const user = users.find(u => u.username === username && u.password === password);
if (user) {
// JWT 생성
const token = jwt.sign({ id: user.id, username: user.username }, 'secretkey', { expiresIn: '1h' });
res.json({ token });
} else {
res.status(401).json({ message: '사용자명 또는 비밀번호가 잘못되었습니다.' });
}
});
app.listen(port, () => {
console.log(`서버가 http://localhost:${port}에서 실행 중입니다.`);
});
이 코드는 사용자가 로그인할 때, username
과 password
를 검증한 후 JWT 토큰을 생성해 클라이언트로 전달합니다. jsonwebtoken
라이브러리를 사용하여 토큰을 생성하고, 이 토큰은 이후 요청에 사용됩니다.
1.2 프론트엔드에서 JWT 저장 및 사용
프론트엔드에서는 사용자가 로그인에 성공하면 받은 JWT 토큰을 저장하고, 이후 요청 시마다 이 토큰을 Authorization 헤더에 포함해 서버로 보냅니다.
// 로그인 함수 (React 예제)
function login(username, password) {
fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username, password }),
})
.then(response => response.json())
.then(data => {
if (data.token) {
localStorage.setItem('token', data.token); // JWT 저장
} else {
alert('로그인 실패');
}
});
}
// API 호출 시 토큰을 함께 전송
function getData() {
const token = localStorage.getItem('token');
fetch('/api/data', {
headers: {
'Authorization': `Bearer ${token}`, // JWT 토큰 전송
},
})
.then(response => response.json())
.then(data => {
console.log(data);
});
}
이 코드는 사용자가 로그인할 때 서버에서 받은 JWT 토큰을 localStorage
에 저장하고, 이후 API 호출 시마다 토큰을 헤더에 포함시켜 서버에 전달합니다.
2. JWT를 이용한 보호된 라우트 구현
사용자가 인증된 경우에만 접근할 수 있는 보호된 라우트(Protected Route)를 구현하려면 백엔드에서 요청에 포함된 JWT 토큰을 검증해야 합니다.
2.1 JWT 검증 미들웨어
서버에서 JWT 토큰을 검증하는 미들웨어를 구현해봅시다.
// JWT 검증 미들웨어
function authenticateToken(req, res, next) {
const token = req.headers['authorization']?.split(' ')[1];
if (!token) return res.status(403).json({ message: '토큰이 필요합니다.' });
jwt.verify(token, 'secretkey', (err, user) => {
if (err) return res.status(403).json({ message: '유효하지 않은 토큰입니다.' });
req.user = user;
next();
});
}
// 보호된 라우트 예시
app.get('/api/protected', authenticateToken, (req, res) => {
res.json({ message: '인증된 사용자만 접근할 수 있습니다.', user: req.user });
});
이 코드는 JWT 검증 미들웨어를 통해 보호된 라우트에 접근할 때마다 토큰을 확인하고, 유효한 경우에만 접근을 허용합니다.
3. 사용자 권한 관리
사용자 인증뿐만 아니라 각 사용자에게 특정 권한(Role)을 부여하여, 그 권한에 맞는 작업만 수행할 수 있도록 해야 합니다. 이를 통해 관리자와 일반 사용자의 접근을 구분할 수 있습니다.
3.1 사용자 권한 설정
사용자에게 role 속성을 추가하여 권한을 관리할 수 있습니다.
// 사용자 데이터에 권한 추가
const users = [
{ id: 1, username: 'user1', password: 'password1', role: 'admin' },
{ id: 2, username: 'user2', password: 'password2', role: 'user' },
];
3.2 권한에 따른 접근 제한
JWT 토큰에 role 정보를 포함하고, 특정 권한을 가진 사용자만 특정 작업을 수행하도록 제한할 수 있습니다.
// 권한 검증 미들웨어
function authorizeRole(role) {
return (req, res, next) => {
if (req.user.role !== role) {
return res.status(403).json({ message: '권한이 없습니다.' });
}
next();
};
}
// 관리자만 접근 가능한 라우트
app.get('/api/admin', authenticateToken, authorizeRole('admin'), (req, res) => {
res.json({ message: '관리자만 접근할 수 있습니다.' });
});
이 코드는 role에 따라 접근을 제한하는 라우트를 구현한 예시입니다. authorizeRole
미들웨어를 통해 관리자만 특정 페이지에 접근할 수 있게 할 수 있습니다.
결론
이번 글에서는 JWT를 이용한 사용자 인증, 보호된 라우트 구현, 사용자 권한 관리까지 풀스택 앱 보안의 필수적인 부분을 다뤘습니다. 이를 통해 앱의 보안을 강화하고, 각 사용자에게 맞는 권한을 부여하여 더욱 안전한 풀스택 앱을 개발할 수 있습니다. 다음 글에서는 앱 성능 최적화와 데이터베이스 보안 강화를 다룰 예정입니다.