[week8] 프로젝트 : Node.js 기반의 Rest API 구현 (13)
![[week8] 프로젝트 : Node.js 기반의 Rest API 구현 (13)](/images/useBlog/TIL.png)
jwt 활용하기
로그인 하면서 jwt 토큰을 발행했었는데 이제 이것을 활용하는 코드 리펙토링을 진행해보겠습니다.
공통 함수 구현
Authorization 함수 요청 헤더에서 JWT 토큰을 추출하고 검증하여 사용자 정보를 반환하는 공통 함수입니다.
// src/utils/auth.js
import jwt from 'jsonwebtoken';
export const Authorization = (req) => {
let receivedJwt = req.headers['authorization'];
let decodedJwt = jwt.verify(receivedJwt, process.env.JWT_SECRET_KEY);
return decodedJwt;
};
요청 헤더에서 JWT 토큰을 추출하고 검증하여 사용자 정보를 반환합니다. 먼저 req.headers["authorization"]으로 클라이언트가 보낸 Authorization 헤더에서 토큰을 추출합니다. 그 다음 jwt.verify()를 사용하여 토큰을 검증합니다. 이 함수는 토큰의 서명이 유효한지, 만료되지 않았는지 확인합니다. process.env.JWT_SECRET_KEY는 토큰을 발급할 때 사용한 비밀키로, 이것과 일치해야만 검증이 성공합니다. 검증에 성공하면 토큰에 포함된 페이로드(예: { id: 5, email: "user@example.com", iat: 1709618400, exp: 1709704800 })를 반환합니다. 검증에 실패하면 에러가 발생합니다.
사용 방법
import { Authorization } from "../utils/auth.js";
const auth = Authorization(req);
좋아요 API
우선 좋아요 API부터 진행해보겠습니다.
Authorization(req)를 호출하여 JWT 토큰에서 현재 사용자의 ID를 추출합니다. 이렇게 하면 누가 어떤 책을 좋아요하고 취소했는지 알 수 있습니다.
좋아요 추가
export const addLike = (req, res) => {
const { liked_book_id } = req.params;
const auth = Authorization(req);
const sql = 'INSERT INTO likes (user_id, liked_book_id) VALUES (?, ?)';
const values = [auth.id, liked_book_id];
conn.query(sql, values, function (err, results) {
if (err) {
console.error('좋아요 추가 DB 에러:', err);
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json(err);
}
return res.status(StatusCodes.CREATED).json({
message: '좋아요 성공!',
result: results,
});
});
};


좋아요 취소
export const removeLike = (req, res) => {
const { liked_book_id } = req.params;
const auth = Authorization(req);
const sql = 'DELETE FROM likes WHERE user_id = ? AND liked_book_id = ?';
const values = [auth.id, liked_book_id];
conn.query(sql, values, function (err, results) {
if (err) {
console.error('좋아요 삭제 DB 에러:', err);
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json(err);
}
return res.status(StatusCodes.CREATED).json({
message: '좋아요 삭제 성공!',
result: results,
});
});
};


장바구니 api
Authorization(req)를 호출하여 JWT 토큰에서 현재 사용자의 ID를 추출하여, 장바구니에 책을 담고, 조회할 수 있습니다.
장바구니 담기
export const addCart = (req, res) => {
const { book_id, quantity } = req.body;
const auth = Authorization(req);
const sql = 'INSERT INTO cart (book_id, quantity, user_id) VALUES (?, ?, ?)';
const values = [book_id, quantity, auth.id];
conn.query(sql, values, (err, results) => {
if (err) {
console.error('장바구니 담기 DB 에러:', err);
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json(err);
}
return res.status(StatusCodes.CREATED).json(results);
});
};
장바구니 조회
export const getCartItems = (req, res) => {
const { selected } = req.body;
const auth = Authorization(req);
let sql = `
SELECT
cart.cart_id,
cart.book_id,
books.title,
books.summary,
cart.quantity,
books.price
FROM cart
LEFT JOIN books ON cart.book_id = books.book_id
WHERE cart.user_id = ?`;
let values = [auth.id];
if (selected && selected.length > 0) {
sql += ` AND cart.cart_id IN (?)`;
values.push(selected);
}
conn.query(sql, values, (err, results) => {
if (err) {
console.error('장바구니 조회 DB 에러:', err);
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json(err);
}
return res.status(StatusCodes.OK).json(results);
});
};
jwt 에러
JWT 검증 과정에서 여러 종류의 에러가 발생할 수 있습니다. 각 에러를 적절히 처리해야 안정적인 API를 구축할 수 있습니다.
TokenExpiredError
토큰의 유효 기간이 만료된 경우 발생합니다. 토큰은 올바른 형식이고 서명도 유효하지만, 토큰의 exp(expiration time) 시간을 초과했을 때 발생합니다. 예를 들어 토큰이 1시간 유효기간으로 설정되었는데 1시간 이상 지났을 때 발생합니다. 이 경우 사용자에게 다시 로그인하도록 안내하고 새로운 토큰을 발급받게 해야 합니다.
JsonWebTokenError
토큰 형식이 올바르지 않거나 서명이 검증되지 않은 경우 발생합니다. 토큰이 변조되었거나 손상되었을 때, 잘못된 SECRET_KEY로 서명되었을 때, 또는 토큰 형식 자체가 유효하지 않을 때 발생합니다. 이 경우 토큰이 신뢰할 수 없으므로 사용자의 로그인 데이터를 삭제하고 다시 로그인하도록 해야 합니다.
try-catch
모든 JWT 관련 에러를 효과적으로 처리하기 위해 try-catch 블록을 사용합니다. try 블록에서 Authorization(req)를 호출하고, 토큰 검증 중에 에러가 발생하면 즉시 catch 블록으로 이동합니다. catch 블록에서 에러의 종류를 확인하여 TokenExpiredError이면 401 상태코드로, JsonWebTokenError이면 401 상태코드로, 그 외 예상하지 못한 에러면 500 상태코드로 응답합니다.
적용하기
auth.js -> handleAuthError 함수 추가
에러 로직을 위하여 함수를 추가했습니다.
export const handleAuthError = (error) => {
if (error.name === 'TokenExpiredError') {
return {
status: StatusCodes.UNAUTHORIZED,
message: '토큰이 만료되었습니다. 다시 로그인해주세요.',
code: 'TOKEN_EXPIRED',
};
}
if (error.name === 'JsonWebTokenError') {
return {
status: StatusCodes.UNAUTHORIZED,
message: '유효하지 않은 토큰입니다.',
code: 'INVALID_TOKEN',
};
}
return {
status: StatusCodes.INTERNAL_SERVER_ERROR,
message: '인증 처리 중 오류가 발생했습니다.',
code: 'AUTH_ERROR',
};
};
try-catch
likeController, cartController에서 구현한 코드들으르 try{ }안으로 감싸고 catch로 auth.js의 handleAuthError를 호출합니다. 적용한 코드를 보여드리겠습니다.
export const addLike = (req, res) => {
try {
const { liked_book_id } = req.params;
const auth = Authorization(req);
const sql = 'INSERT INTO likes (user_id, liked_book_id) VALUES (?, ?)';
const values = [auth.id, liked_book_id];
conn.query(sql, values, function (err, results) {
if (err) {
console.error('좋아요 추가 DB 에러:', err);
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json(err);
}
return res.status(StatusCodes.CREATED).json({
message: '좋아요 성공!',
result: results,
});
});
} catch (error) {
console.error('좋아요 추가 에러:', error);
const { status, message, code } = handleAuthError(error);
return res.status(status).json({ message, code });
}
};
