2024년 10월 2일
// mariadb.js
// mysql 모듈 소환
const mariadb = require("mysql2");
// DB와 연결 통로 생성
const connection = mariadb.createConnection({
host: "127.0.0.1",
user: "root",
password: "root",
database: "Bookshop",
dateStrings: true
});
module.exports = connection;
// users.js
const express = require("express");
const router = express.Router();
const conn = require('../mariadb');
router.use(express.json());
// 회원가입
router.post("/join", (req, res) => {
const {email, password} = req.body;
let sql = 'INSERT INTO users (email, password) VALUES (?, ?)';
let values = [email, password];
conn.query(sql, values, (err, results) => {
if(err) {
console.log(err);
return res.status(400).end(); // BAD REQUEST
}
res.status(201).json(results);
});
});
// users.js
const express = require("express"); // express 모듈
const router = express.Router();
const conn = require("../mariadb"); // db 모듈
const { StatusCodes } = require("http-status-codes"); // status code 모듈
router.use(express.json());
// 회원가입
router.post("/join", (req, res) => {
const { email, password } = req.body;
let sql = "INSERT INTO users (email, password) VALUES (?, ?)";
let values = [email, password];
conn.query(sql, values, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
return res.status(StatusCodes.CREATED).json(results);
});
});
... 생략 ...
POSTMAN) POST + localhost:9999/users/join + {”email” : “kim@mail.com”, “password” : 1111}
node.js 패키지 구조를 보면,
그런데, 라우터는 경로를 찾아주는 역할만 하는데 우리는 로직까지 다 짜주고 있다
한곳에 코드를(로직을) 다 작성하면 나중에 수정하기 어려움
라우터가 로직까지 다 수행할 때 단점
- 프로젝트 규모가 커질수록, 코드가 복잡해짐
- 가독성 떨어짐
- 트러블 슈팅(에러를 찾아서 해결하는 작업) 어려움
⇒ “유지 보수” 하기 어려움
*유지보수 : 프로그램을 계속 운영하면서 요구사항을 반영하고 에러를 해결하는 것을 말함
⇒ 해결 방법 : 코드를 간결하고, 가독성이 높게 만들어주는 것!!
어쨌든, 그래서 코드를 빼내야함 -> 콜백함수를 빼내야함!!
- 콜백함수 ⇒ 경로를 찾은 다음 역할
컨트롤러 : 프로젝트에서 매니저 역할을 하는 파일 : 관장
- 누군가에게 일을 어떻게 시켜야할지 알고 있음(= 직접 일을 하진 않을 것)
- 라우터를 통해서 “사용자의 요청(request)이” 길(url)을 찾아오면 매니저(콜백함수 = controller)가 환영해 줄 것임
- 그리고 매니저는 직접 일을 하진 않고, 알바생(service)한테 일을 시키고, 알바생이 일을 한다음 결과물을 매니저에게 전달
- 그다음, 매니저(controller)는 사용자에게 response를 돌려준다.
라우터는 길을 찾는 용도로만 사용!
컨트롤러를 만들어보자
// controller / UserController.js
const conn = require("../mariadb"); // db 모듈
const { StatusCodes } = require("http-status-codes"); // status code 모듈
const join = (req, res) => {
const { email, password } = req.body;
let sql = "INSERT INTO users (email, password) VALUES (?, ?)";
let values = [email, password];
conn.query(sql, values, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
return res.status(StatusCodes.CREATED).json(results);
});
};
module.exports = join;
const express = require("express"); // express 모듈
const router = express.Router();
const conn = require("../mariadb"); // db 모듈
const { StatusCodes } = require("http-status-codes"); // status code 모듈
router.use(express.json());
const join = require("../controller/UserController");
// 회원가입
router.post("/join", join);
... 생략 ...
POSTMAN) POST + localhost:9999/users/join + {”email” : “lee@email.com”, “password” : “2222”}
POST + localhost:9999/users/join + {”email” : “park@email.com”, “password” : “3333”}
// UserController.js
const conn = require("../mariadb"); // db 모듈
const { StatusCodes } = require("http-status-codes"); // status code 모듈
const join = (req, res) => {
const { email, password } = req.body;
let sql = "INSERT INTO users (email, password) VALUES (?, ?)";
let values = [email, password];
conn.query(sql, values, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
return res.status(StatusCodes.CREATED).json(results);
});
};
const login = (req, res) => {
res.json("로그인");
};
const passwordResetRequest = (req, res) => {
res.json("비밀번호 초기화 요청");
};
const passwordReset = (req, res) => {
res.json("비밀번호 초기화");
};
module.exports = { join, login, passwordResetRequest, passwordReset };
// users.js
const express = require("express"); // express 모듈
const router = express.Router();
const conn = require("../mariadb"); // db 모듈
const { StatusCodes } = require("http-status-codes"); // status code 모듈
router.use(express.json());
const {
join,
login,
passwordResetRequest,
passwordReset,
} = require("../controller/UserController");
router.post("/join", join); // 회원가입
router.post("/login", login); // 로그인
router.post("/reset", passwordResetRequest); // 비밀번호 초기화 요청
router.put("/reset", passwordReset); // 비밀번호 초기화
module.exports = router;
우리는 response를 쿠키로 보낼 예정이므로 api설계를 수정한다.
// UserController.js
const conn = require("../mariadb"); // db 모듈
const { StatusCodes } = require("http-status-codes"); // status code 모듈
const jwt = require("jsonwebtoken"); // jwt 모듈
const dotenv = require("dotenv"); // dotenv 모듈
dotenv.config();
const login = (req, res) => {
const { email, password } = req.body;
let sql = `SELECT * FROM users WHERE email = ?`;
conn.query(sql, email, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
const loginUser = results[0];
if (loginUser && loginUser.password == password) {
// 토큰 발행
const token = jwt.sign(
{
email: loginUser.email,
},
process.env.PRIVATE_KEY,
{
expiresIn: "5m",
issuer: "hongbi",
}
);
// 토큰 쿠키에 담기
res.cookie("token", token, { httpOnly: true });
console.log(token);
res.status(StatusCodes.OK).json(results);
} else {
res.status(StatusCodes.UNAUTHORIZED).end();
// 403 : Forbidden (접근 권리 없음)
// 401 : Unauthorized (미인증 상태)
// 403은 접근 권리가 없다는 뜻인데, 서버는 그 사람이 누구인지 알고 있지만
// 401은 그 사람이 누군지 모름
}
});
};
... 생략 ...
POSTMAN) POST + localhost:9999/users/login + {”email” : “kim@email.com”, “password” : “1111”}
// UserController.js
const conn = require("../mariadb"); // db 모듈
const { StatusCodes } = require("http-status-codes"); // status code 모듈
const jwt = require("jsonwebtoken"); // jwt 모듈
const dotenv = require("dotenv"); // dotenv 모듈
dotenv.config();
const passwordResetRequest = (req, res) => {
const { email } = req.body;
let sql = `SELECT * FROM users WHERE email = ?`;
conn.query(sql, email, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
const user = results[0];
if (user) {
return res.status(StatusCodes.OK).json({
email: email,
});
} else {
return res.status(StatusCodes.UNAUTHORIZED).end();
}
});
};
if(results.affectedRows == 0) {
return res.status(StatusCodes.BAD_REQUEST).end();
} else {
return res.status(StatusCodes.OK).json(results);
}
// UserController.js
const conn = require("../mariadb"); // db 모듈
const { StatusCodes } = require("http-status-codes"); // status code 모듈
const jwt = require("jsonwebtoken"); // jwt 모듈
const dotenv = require("dotenv"); // dotenv 모듈
dotenv.config();
const passwordReset = (req, res) => {
const { email, password } = req.body;
let sql = `UPDATE users SET password = ? WHERE email = ?`;
let values = [password, email];
conn.query(sql, values, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
if (results.affectedRows == 0) {
return res.status(StatusCodes.BAD_REQUEST).end();
} else {
return res.status(StatusCodes.OK).json(results);
}
});
};
비밀번호 초기화 요청 : POSTMAN) POST + localhost:9999/users/reset + {”email” : “kim@email.com”}
비밀번호 초기화 : PUT + localhost:9999/users/reset + {”email” : “kim@email.com”, “password” : “1010”}
crypto : node.js에서 제공하는 기본 내장모듈로서 암호화에 사용이 된다.
// UserController.js
const conn = require("../mariadb"); // db 모듈
const { StatusCodes } = require("http-status-codes"); // status code 모듈
const jwt = require("jsonwebtoken"); // jwt 모듈
const crypto = require("crypto"); // crypto 모듈 : 암호화
const dotenv = require("dotenv"); // dotenv 모듈
dotenv.config();
// UserController.js
const conn = require("../mariadb"); // db 모듈
const { StatusCodes } = require("http-status-codes"); // status code 모듈
const jwt = require("jsonwebtoken"); // jwt 모듈
const crypto = require("crypto"); // crypto 모듈 : 암호화
const dotenv = require("dotenv"); // dotenv 모듈
dotenv.config();
const join = (req, res) => {
const { email, password } = req.body;
// 비밀번호 암호화
const salt = crypto.randomBytes(64).toString("base64");
const hashPassword = crypto
.pbkdf2Sync(password, salt, 10000, 64, "sha512")
.toString("base64");
let sql = "INSERT INTO users (email, password) VALUES (?, ?)";
let values = [email, password];
conn.query(sql, values, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
return res.status(StatusCodes.CREATED).json(results);
});
};
salt : hashPassword를 만들어주기 위한것(= 비밀번호를 암호화하는데 랜덤한 값을 넣어 늘 새로운 암호화 비밀번호를 만들 수 있게 도와주는 난수)
hashPassword
테스트해보자
// demo-node / crypto-demo.js
const crypto = require('crypto');
const password = "1111";
const salt = crypto.randomBytes(64).toString("base64");
const hashPassword = crypto
.pbkdf2Sync(password, salt, 10000, 10, "sha512")
.toString("base64");
console.log(hashPassword);
서버 재작동 하면, 비밀번호 다시 달라짐
그러나!!!이 알고리즘은 단방향임
그럼 어떻게 로그인 해야할까?
이제 salt를 insert해보자
// Usercontroller.js
const conn = require("../mariadb"); // db 모듈
const { StatusCodes } = require("http-status-codes"); // status code 모듈
const jwt = require("jsonwebtoken"); // jwt 모듈
const crypto = require("crypto"); // crypto 모듈 : 암호화
const dotenv = require("dotenv"); // dotenv 모듈
dotenv.config();
const join = (req, res) => {
const { email, password } = req.body;
let sql = "INSERT INTO users (email, password, salt) VALUES (?, ?, ?)";
// 회원 가입시 비밀번호를 암호화해서 암호화된 비밀번호와 salt 값을 같이 저장
const salt = crypto.randomBytes(10).toString("base64");
const hashPassword = crypto
.pbkdf2Sync(password, salt, 10000, 10, "sha512")
.toString("base64");
let values = [email, hashPassword, salt];
// 로그인 시, 이메일 & 비밀번호(날 것) => salt값 꺼내서 비밀번호 암호화 해보고 => 디비 비밀번호랑 비교
conn.query(sql, values, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
return res.status(StatusCodes.CREATED).json(results);
});
};
POSTMAN) POST + localhost:9999/users/join + {”email” : “choi@mail.com”, “password” : “4444”}
const hashPassword = crypto
.pbkdf2Sync(password, loginUser.salt, 10000, 10, "sha512")
.toString("base64");
// Usercontroller.js
const conn = require("../mariadb"); // db 모듈
const { StatusCodes } = require("http-status-codes"); // status code 모듈
const jwt = require("jsonwebtoken"); // jwt 모듈
const crypto = require("crypto"); // crypto 모듈 : 암호화
const dotenv = require("dotenv"); // dotenv 모듈
dotenv.config();
const login = (req, res) => {
const { email, password } = req.body;
let sql = `SELECT * FROM users WHERE email = ?`;
conn.query(sql, email, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
const loginUser = results[0];
// salt값 꺼내서 날 것으로 들어온 비밀번호를 암호화 해보고
const hashPassword = crypto
.pbkdf2Sync(password, loginUser.salt, 10000, 10, "sha512")
.toString("base64");
// => 디비 비밀번호랑 비교
if (loginUser && loginUser.password == hashPassword) {
// 토큰 발행
const token = jwt.sign(
{
email: loginUser.email,
},
process.env.PRIVATE_KEY,
{
expiresIn: "5m",
issuer: "hongbi",
}
);
// 토큰 쿠키에 담기
res.cookie("token", token, { httpOnly: true });
console.log(token);
res.status(StatusCodes.OK).json(results);
} else {
res.status(StatusCodes.UNAUTHORIZED).end();
}
});
};
POSTMAN) POST + localhost:9999/users/login + {”choi@email.com”, “password” : “4444”}
// UserControoler.js
const conn = require("../mariadb"); // db 모듈
const { StatusCodes } = require("http-status-codes"); // status code 모듈
const jwt = require("jsonwebtoken"); // jwt 모듈
const crypto = require("crypto"); // crypto 모듈 : 암호화
const dotenv = require("dotenv"); // dotenv 모듈
dotenv.config();
const passwordReset = (req, res) => {
const { email, password } = req.body;
let sql = `UPDATE users SET password = ?, salt = ? WHERE email = ?`;
// 암호화된 비밀번호와 salt 값을 같이 DB에 저장
const salt = crypto.randomBytes(10).toString("base64");
const hashPassword = crypto
.pbkdf2Sync(password, salt, 10000, 10, "sha512")
.toString("base64");
let values = [hashPassword, salt, email];
conn.query(sql, values, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
if (results.affectedRows == 0) {
return res.status(StatusCodes.BAD_REQUEST).end();
} else {
return res.status(StatusCodes.OK).json(results);
}
});
};
POSTMAN) PUT + localhost:9999/users/reset + {”email” : “kim@email.com”, “password” : “1111”}
🍏🍎 오늘의 느낀점 : 와우,, 오늘 실습은 정말 흥미로웠다. 비밀번호를 암호화하는 새로운 방법을 배웠는데 모듈을 통해 비교적 쉽게(?) 할 수 있는 방법을 알아서 나중에 꼭 써먹어봐야겠다. 그리고 statuscode도 모듈을 통해 프론트단에게 전달해주려고 문자열로 연결해주는 방법이 있는 것을 알았고, 역시 오류를 없애려고 애써야 겠구나 하는 생각도 들었다. 그리고 로직들을 짜면서 api 설계가 바뀌면서 요청, 응답을 어떻게 해줘야 더 좋은 로직이 될지 많은 고민을 해보야겠구나 하는 생각이 들었다.