2024년 10월 16일
: jwt 토큰을 발행하는 것을 테스트해보자.
// authorization-demo.js
const express = require('express');
const app = express();
const dotenv = require('dotenv');
dotenv.config();
const jwt = require('jsonwebtoken');
// 서버셋팅 : 포트넘버 1234로 셋팅
app.listen(process.env.PORT);
// 토큰 발행
app.get('/', function (req, res) {
const token = jwt.sign({foo: 'bar'}, process.env.PRIVATE_KEY);
res.cookie("jwt", token, {httpOnly: true});
res.send('토큰 발행 완료!');
});
// 토큰 검증
app.get('/', function (req, res) {
res.send('Hello World');
});
POSTMAN) GET + localhost:1234
결과를 보았을때, response header의 set-cookies에 jwt가 잘 들어와있는 것을 볼 수 있다.
그런데 request header의 cookie를 보면 jwt와 token을 볼 수 있다.
// authorization-demo.js
const express = require("express");
const app = express();
const dotenv = require("dotenv");
dotenv.config();
const jwt = require("jsonwebtoken");
// 서버셋팅 : 포트넘버 1234로 셋팅
app.listen(process.env.PORT);
// GET + "/jwt" : 토큰 발행
app.get("/jwt", function (req, res) {
const token = jwt.sign({ foo: "bar" }, process.env.PRIVATE_KEY);
res.cookie("jwt", token, { httpOnly: true });
res.send("토큰 발행 완료!");
});
// GET + "/jwt/decoded" : 토큰 검증
app.get("/jwt/decoded", function (req, res) {
let receivedJwt = req.headers["authorization"];
console.log("우리가 req로 전달받은 jwt : ", jwt);
const decoded = jwt.verify(receivedJwt, process.env.PRIVATE_KEY);
res.send(decoded);
});
jwt 토큰을 발행하고
그리고 보안으로 위해 Authorization에 jwt를 복사해서 붙여 넣어준다
payload와 발행기간이 나온것을 확인할 수 있다.
// LikeController.js
const conn = require("../mariadb"); // db 모듈
const { StatusCodes } = require("http-status-codes"); // status code 모듈
const jwt = require("jsonwebtoken");
const dotenv = require("dotenv");
dotenv.config();
const addLike = (req, res) => {
const { id } = req.params; // book_id
// const { user_id } = req.body;
let receivedJwt = req.headers["authorization"];
console.log("received jwt : ", receivedJwt);
let decodedJwt = jwt.verify(receivedJwt, process.env.PRIVATE_KEY);
console.log(decodedJwt);
}
login을 먼저하고, 토큰을 복사해서 authorization에 넣어준다.
그리고 localhost:9999/likes/3 + authorizaiton : 복사한 토큰 → response로 준게 없기 때문에 계속 로딩중이지만, 콘솔창을 보면 받은 토큰(recieved jwt)를 확인할 수 있고, 복호화한 payload 정보들을 얻을 수 있다. (이메일, 발행시점, 유효시점, 발행자)
그런데 우리는 likes 테이블에 user_id도 넣어줘야하므로 토큰의 payload값에 id도 넣어주자.
// UserController.js
... 생략 ...
if (loginUser && loginUser.password == hashPassword) {
// 토큰 발행
const token = jwt.sign(
{
id: loginUser.id,
email: loginUser.email,
},
... 생략 ...
다시 로그인하고 좋아요 POSTMAN으로 보내면
id값이 잘 들어간 것을 볼 수 있다.
이제 likes 테이블에 토큰을 통해 id를 insert해보자.
// LikeController.js
const conn = require("../mariadb"); // db 모듈
const { StatusCodes } = require("http-status-codes"); // status code 모듈
const jwt = require("jsonwebtoken");
const dotenv = require("dotenv");
dotenv.config();
const addLike = (req, res) => {
const { id } = req.params; // book_id
// const { user_id } = req.body;
let receivedJwt = req.headers["authorization"];
console.log("received jwt : ", receivedJwt);
let decodedJwt = jwt.verify(receivedJwt, process.env.PRIVATE_KEY);
console.log(decodedJwt);
let sql = "INSERT INTO likes (user_id, liked_book_id) VALUES (?, ?)";
let values = [decodedJwt.id, id];
conn.query(sql, values, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
return res.status(StatusCodes.OK).json(results);
});
};
POSTMAN) POST + localhost:9999/likes/3 + authorization with token
좋아요 추가 전 데이터베이스 likes 테이블
좋아요 추가 후 데이터베이스 likes 테이블
// LikeController.js
const conn = require("../mariadb"); // db 모듈
const { StatusCodes } = require("http-status-codes"); // status code 모듈
const jwt = require("jsonwebtoken");
const dotenv = require("dotenv");
dotenv.config();
const removeLike = (req, res) => {
const { id } = req.params;
let receivedJwt = req.headers["authorization"];
console.log("received jwt : ", receivedJwt);
let decodedJwt = jwt.verify(receivedJwt, process.env.PRIVATE_KEY);
console.log(decodedJwt);
let sql = "DELETE FROM likes WHERE user_id = ? AND liked_book_id = ?";
let values = [decodedJwt.id, id];
conn.query(sql, values, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
return res.status(StatusCodes.OK).json(results);
});
};
POSTMAN) DELETE + localhost:9999/likes/3
const conn = require("../mariadb"); // db 모듈
const { StatusCodes } = require("http-status-codes"); // status code 모듈
const jwt = require("jsonwebtoken");
const dotenv = require("dotenv");
dotenv.config();
const addLike = (req, res) => {
const book_id = req.params.id;
let authorization = ensureAuthorization(req);
let sql = "INSERT INTO likes (user_id, liked_book_id) VALUES (?, ?)";
let values = [authorization.id, book_id];
conn.query(sql, values, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
return res.status(StatusCodes.OK).json(results);
});
};
const removeLike = (req, res) => {
const book_id = req.params.id;
let authorization = ensureAuthorization(req);
let sql = "DELETE FROM likes WHERE user_id = ? AND liked_book_id = ?";
let values = [authorization.id, book_id];
conn.query(sql, values, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
return res.status(StatusCodes.OK).json(results);
});
};
function ensureAuthorization(req) {
let receivedJwt = req.headers["authorization"];
console.log("received jwt : ", receivedJwt);
let decodedJwt = jwt.verify(receivedJwt, process.env.PRIVATE_KEY);
console.log(decodedJwt);
return decodedJwt;
}
module.exports = { addLike, removeLike };
// CartController.js
const conn = require("../mariadb"); // db 모듈
const { StatusCodes } = require("http-status-codes"); // status code 모듈
const jwt = require("jsonwebtoken");
const dotenv = require("dotenv");
dotenv.config();
const addToCart = (req, res) => {
const { book_id, quantity } = req.body;
let authorization = ensureAuthorization(req);
let sql =
"INSERT INTO cartItems (book_id, quantity, user_id) VALUES (?, ?, ?)";
let values = [book_id, quantity, authorization.id];
conn.query(sql, values, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
return res.status(StatusCodes.OK).json(results);
});
};
const getCartItems = (req, res) => {
let { selected } = req.body; // selected = [1, 3]
let authorization = ensureAuthorization(req);
let sql = `SELECT cartItems.id, book_id, title, summary, quantity, price
FROM cartItems LEFT JOIN books
ON cartItems.book_id = books.id
WHERE user_id = ? AND cartItems.id IN (?)`;
let values = [authorization.id, selected];
conn.query(sql, values, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
return res.status(StatusCodes.OK).json(results);
});
};
const removeCartItem = (req, res) => {
let cartItemId = req.params.id;
let sql = "DELETE FROM cartItems WHERE id = ?";
conn.query(sql, cartItemId, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
return res.status(StatusCodes.OK).json(results);
});
};
POSTMAN) 로그인 + POST + localhost:9999/carts + authorizaiton:복사한 token + { ”book_id” : 7, “quantity”: 1 }
POSTMAN) 로그인 + GET + localhost:9999/carts + authorizaiton:복사한 token + { “selected” : [4, 5] }
POSTMAN) 로그인 + DELETE + localhost:9999/carts /5
: 유효기간이 만료되면 에러가 난다. 이것은 사용자에게 좋지 않으므로 예외처리를 해줘야함!
일단, 유효기간 만료되게 셋팅 먼저.
// authorization-demo.js
const express = require("express");
const app = express();
const dotenv = require("dotenv");
dotenv.config();
const jwt = require("jsonwebtoken");
// 서버셋팅 : 포트넘버 1234로 셋팅
app.listen(process.env.PORT);
// GET + "/jwt" : 토큰 발행
app.get("/jwt", function (req, res) {
const token = jwt.sign(
{
username: "rururu",
},
process.env.PRIVATE_KEY,
{
expiresIn: "1m",
issuer: "admin",
}
);
res.cookie("jwt", token, { httpOnly: true });
res.send("토큰 발행 완료!");
});
jwt 토큰을 발해하고, 검증까지 했음
그리고나서 유효기간이 1분 지난 상태에서 다시 검증 시도를 하면 500 에러가 뜨는 것을 알 수 있다.
콘솔창을 확인하면 jwt가 expired됐다는 것을 알 수 있다.
if(실수1) {
} else if(실수2) {
}
만약 실수 100개라면 100개를 다 써줘야함
그런데 실수1, 실수2 … 비슷한 분류들끼리 묶어서 누군가 따로 관리해주면 좋겠다 → try catch를 이용
A 코드 실행;
if (A에서 발생한 실수1) {
} else if (A에서 발생한 실수2) {
} …
이 코드를 try catch문으로 바꾸면
try catch문을 직접 작성해서 어떻게 되는지 실행해보자
// try-catch-demo.js
try {
username;
} catch (err) {
console.log("username이 선언되지 않았습니다.");
console.log("발생한 에러는 다음과 같습니다.");
console.log(err);
}
// try-catch-demo.js
let string = '{ "num1":1 ';
try {
let json = JSON.parse(string);
console.log(json);
} catch (err) {
console.log(err);
}
// try-catch-demo.js
let string = '{ "num1":1 ';
try {
username;
let json = JSON.parse(string);
console.log(json);
} catch (err) {
console.log(err);
}
// try-catch-demo.js
let string = '{ "num1":1 ';
try {
// username;
let json = JSON.parse(string);
console.log(json);
} catch (err) {
console.log(err.name);
console.log(err.message);
// 에러 객체의 프로퍼티를 콘솔에 찍어보자.
}
// throw-demo.js
let error = new Error("대장 에러 객체");
let syntaxError = new SyntaxError("구문 에러 발생");
let referenceError = new ReferenceError("대입 에러 발생");
console.log(error.name);
console.log(error.message);
console.log(syntaxError.name);
console.log(syntaxError.message);
console.log(referenceError.name);
console.log(referenceError.message);
// try-catch-demo.js
let string = '{ "num1":1 }';
try {
// username;
let json = JSON.parse(string);
console.log(json.name);
} catch (err) {
console.log(err.name);
console.log(err.message);
console.log(err);
}
이름이 없으니 undefined가 나오는 것보단 에러를 던져줘야한다 → js 입장에서는 에러가 아니지만, 우리 입장에서는 에러 ⇒ 입력값이 잘못된 에러
if else문으로 예외 처리 해보자.
// try-catch-demo.js
let string = '{ "num1":1 }';
try {
// username;
let json = JSON.parse(string);
if (!json.name) {
console.log("입력 값에 이름이 없습니다.");
} else {
console.log(json.name); // js 입장에선 에러가 아니지만, 우리 입장에선 에러 = 입력값이 잘못된 에러
}
let name = json.name;
console.log(name);
} catch (err) {
console.log(err.name);
console.log(err.message);
console.log(err);
}
// try-catch-demo.js
let string = '{ "num1":1 }';
try {
// username;
let json = JSON.parse(string);
if (!json.name) {
throw new SyntaxError("입력 값에 이름이 없습니다.");
} else {
console.log(json.name); // js 입장에선 에러가 아니지만, 우리 입장에선 에러 = 입력값이 잘못된 에러
}
let name = json.name; // 이름이 있으면
console.log(name);
} catch (err) {
console.log(err.name);
console.log(err.message);
console.log(err);
}
throw를 던져주자마자 밑의 코드를 읽지 않고 바로 catch로 넘어가는 것을 알 수 있음 → 우리가 발생시킨 에러 객체가 들어가는 것 확인할 수 있음
// CartController.js
... 생략 ...
function ensureAuthorization(req, res) {
try {
let receivedJwt = req.headers["authorization"];
console.log("received jwt : ", receivedJwt);
let decodedJwt = jwt.verify(receivedJwt, process.env.PRIVATE_KEY);
console.log(decodedJwt);
return decodedJwt;
} catch (err) {
console.log(err.name);
console.log(err.message);
return res.status(StatusCodes.UNAUTHORIZED).json({
message: "로그인 세션이 만료되었습니다. 다시 로그인 하세요.",
});
}
}
... 생략 ...
로그인 + jwt token 복사해서 POSTMAN) localhost:9999/carts + {”selected” : [4] } + header에 authorizaiton & 복사한 token 붙여넣어서 확인
그런데, throw 다음 에러 객체가 있는 것을 확인할 수 있다 ⇒ ERR_HTTP_HEADERS_SENT()
// CartController.js
... 생략 ...
const getCartItems = (req, res) => {
let { selected } = req.body; // selected = [1, 3]
let authorization = ensureAuthorization(req, res);
if (authorization instanceof jwt.TokenExpiredError) {
return res.status(StatusCodes.UNAUTHORIZED).json({
message: "로그인 세션이 만료되었습니다. 다시 로그인 하세요.",
});
} else {
let sql = `SELECT cartItems.id, book_id, title, summary, quantity, price
FROM cartItems LEFT JOIN books
ON cartItems.book_id = books.id
WHERE user_id = ? AND cartItems.id IN (?)`;
let values = [authorization.id, selected];
conn.query(sql, values, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
return res.status(StatusCodes.OK).json(results);
});
}
};
function ensureAuthorization(req, res) {
try {
let receivedJwt = req.headers["authorization"];
console.log("received jwt : ", receivedJwt);
let decodedJwt = jwt.verify(receivedJwt, process.env.PRIVATE_KEY);
console.log(decodedJwt);
return decodedJwt;
} catch (err) {
console.log(err.name);
console.log(err.message);
return err;
}
}
... 생략 ...
// CartController.js
... 생략 ...
const getCartItems = (req, res) => {
let { selected } = req.body; // selected = [1, 3]
let authorization = ensureAuthorization(req, res);
if (authorization instanceof jwt.TokenExpiredError) {
return res.status(StatusCodes.UNAUTHORIZED).json({
message: "로그인 세션이 만료되었습니다. 다시 로그인 하세요.",
});
} else if (authorization instanceof jwt.JsonWebTokenError) {
return res.status(StatusCodes.BAD_REQUEST).json({
message: "잘못된 토큰입니다.",
});
} else {
let sql = `SELECT cartItems.id, book_id, title, summary, quantity, price
FROM cartItems LEFT JOIN books
ON cartItems.book_id = books.id
WHERE user_id = ? AND cartItems.id IN (?)`;
let values = [authorization.id, selected];
conn.query(sql, values, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
return res.status(StatusCodes.OK).json(results);
});
}
};
... 생략 ...
ensureAuthorizaition()을 쓰는 다른 API들도 고치기(addCart, addLike, removeLike)
그런데, 코드를 보면 ensureAuthorizaition()함수가 계속 반복되고, 호출해서 사용하는 부분도 계속 반복되는 것을 볼수 있음 -> 외부모듈로 빼서 관리해줘야할 필요 있음
🍎🍏 오늘의 느낀점 : 오늘은 토큰을 발행하고 검증하는 연습부터 직접 프로젝트에 넣어보면서 많은것들을 배웠다. try, catch문을 간단히 알고 있었는데 어떤 구문인지 확실하게 알 수 있었다.