프로젝트 보안 고려사항, 보강 시나리오 2를 목표


사용자의 거래 내역을 불러올때마다 DB를 연결해 받아오거나 클라이언트에서 모든 내역을 저장해두고 정렬하거나 페이징 하는 기존 코드를
프론트엔드 서버에서 캐시를 사용하여 서버 연결 후 처음으로 거래 내역을 불러올때 DB에서 받아오면서 캐시에 저장하여 거래 내역이 추가(입출금, 송금 등)되지 않는 이상 저장된 캐시를 사용해 클라이언트에게 응답하는 코드로 수정
asset-management.js(Frontend)
수정 전
// 자산관리 거래 내역 불러오기
router.get('/transactions', async function (req, res) {
try {
const response = await fetch(`http://127.0.0.1:8000/amm/transactions`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'User-Id': req.session.user.userid
}
});
const data = await response.json();
if (response.ok) {
res.json(data);
} else {
res.status(response.status).json(data);
}
} catch (error) {
console.error(error);
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
}
});
수정 후
// 자산관리 거래 내역 불러오기
// 캐시가 존재하면 DB에 접근하지 않고 저장된 캐시를 사용해 응답
router.get('/transactions', async function (req, res) {
const userId = req.session.user.userid;
const accountNumber = req.query.account;
const transactionType = req.query.filter || 'all';
const page = parseInt(req.query.page) || 1;
const rowsPerPage = 10;
let transactions = transactionCache.get(userId);
if (!transactions) {
try {
const response = await fetch(`http://127.0.0.1:8000/amm/transactions`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'User-Id': userId,
}
});
const data = await response.json();
if (response.ok) {
transactionCache.set(userId, data);
transactions = data;
} else {
return res.status(response.status).json(data);
}
} catch (error) {
console.error(error);
return res.status(500).json({ message: '서버 오류가 발생했습니다.' });
}
}
// 필터링
let filteredTransactions = transactions;
if (accountNumber && accountNumber !== '') {
filteredTransactions = filteredTransactions.filter(transaction => transaction.account_number === accountNumber);
}
if (transactionType !== 'all') {
filteredTransactions = filteredTransactions.filter(transaction => transaction.transaction_type === transactionType);
}
// 페이징
const totalTransactions = filteredTransactions.length;
const pageCount = Math.ceil(totalTransactions / rowsPerPage);
const paginatedTransactions = filteredTransactions.slice((page - 1) * rowsPerPage, page * rowsPerPage);
res.json({
transactions: paginatedTransactions,
page,
pageCount,
totalTransactions
});
});
asset-management.js(Backend)
// 자산관리 거래 내역 불러오기
router.get('/transactions', async function (req, res) {
const { mysqldb } = await setup();
const sessionUser = req.headers['user-id'];
if (!sessionUser) {
return res.status(401).json({ message: '인증되지 않은 사용자' });
}
// const accountNumber = req.query.account || null; // 필터링 코드 삭제
// const transactionType = req.query.filter || 'all';
try {
// 사용자 ID로 계좌 정보 가져오기
const [accounts] = await mysqldb.promise().query('SELECT account_number, user_id FROM accounts WHERE user_id = (SELECT id FROM users WHERE userid = ?)', [sessionUser]);
if (accounts.length === 0) {
return res.status(404).json({ message: '해당 사용자의 계좌를 찾을 수 없습니다.' });
}
// 계좌 번호가 있는 경우, 해당 계좌의 소유자 확인 // 필터링 코드 삭제
// if (accountNumber) {
// const [account] = await mysqldb.promise().query('SELECT user_id FROM accounts WHERE account_number = ?', [accountNumber]);
// if (account.length === 0 || account[0].user_id !== accounts[0].user_id) {
// return res.status(403).json({ message: '해당 계좌에 접근 권한이 없습니다.' });
// }
// }
// 기본 쿼리
let query = 'SELECT * FROM transactions WHERE account_number IN (SELECT account_number FROM accounts WHERE user_id = (SELECT id FROM users WHERE userid = ?))';
const queryParams = [sessionUser];
// if (accountNumber) { // 필터링 코드 삭제
// query += ' AND account_number = ?';
// queryParams.push(accountNumber);
// }
//
// if (transactionType !== 'all') {
// query += ' AND transaction_type = ?';
// queryParams.push(transactionType);
// }
query += ' ORDER BY transaction_date DESC';
// 거래 내역 조회
const [transactions] = await mysqldb.promise().query(query, queryParams);
return res.status(200).json(transactions);
} catch (err) {
console.error(err);
return res.status(500).json({ message: '서버 오류 발생' });
}
});
기존 특정 계좌의 잔액 불러오는 코드도 캐시를 사용하도록 수정
asset-management.js(Frontend)
// 자산관리 특정 계좌의 잔액 불러오기
router.get('/account-balance/:accountNumber', async function (req, res) {
const userId = req.session.user.userid;
const accountNumber = req.params.accountNumber;
const transactions = transactionCache.get(userId);
if (transactions) {
const accountTransactions = transactions.filter(transaction => transaction.account_number === accountNumber);
if (accountTransactions.length > 0) {
const latestTransaction = accountTransactions.reduce((latest, transaction) => {
return new Date(transaction.transaction_date) > new Date(latest.transaction_date) ? transaction : latest;
});
return res.json({ balance: latestTransaction.balance });
}
}
try {
const response = await fetch(`http://127.0.0.1:8000/amm/account-balance/${accountNumber}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'User-Id': userId,
}
});
const data = await response.json();
if (response.ok) {
res.json(data);
} else {
res.status(response.status).json(data);
}
} catch (error) {
console.error(error);
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
}
});
DB가 수정되면 캐시 초기화
asset-managemnet.js(Frontend)
// 자산관리 송금 처리
router.post('/transfer', async function (req, res) {
try {
const response = await fetch('http://127.0.0.1:8000/amm/transfer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'User-Id': req.session.user.userid
},
body: JSON.stringify({ ...req.body })
});
const data = await response.json();
if (response.ok) {
transactionCache.del(req.session.user.userid); // 캐시 초기화
res.status(201).send(data);
} else {
res.status(response.status).send(data);
}
} catch (error) {
console.error(error);
}
});