고양이와 코딩
[웹 풀사이클 데브코스 TIL] 8주차 Day 1 - API 설계, crypto를 사용한 암호화 본문
http-status-codes
if(err){
return res.status(400).end()
}
지금까지는 에러를 만나면 이런 식으로 status code를 보내줬는데요,
사람은 누구나.. 언제나 실수할 수 있기 때문에 status code가 잘못 전달되서 프론트엔드 단에 혼란을 주기보다는 상태 코드를 변수에 담아서 보내면 좋을 것 같습니다!
이를 위해 npm에서 제공하고 있는 모듈이 있는데요
const {StatusCodes} = require('http-status-codes');
conn.query(sql, values,
(err, results) => {
if(err){
return res.status(StatusCodes.BAD_REQUEST).end()
}
res.status(StatusCodes.CREATED).json(results)
}
);
});
이렇게 사용 할 수 있습니다 😺
파일을 분리해보자 !
프로젝트 규모가 커질수록 현재 코드처럼 라우터가 로직까지 다 수행하고 있다면, 유지보수와 확장성 측면에서 문제가 발생할 수 있습니다.
- 가독성과 유지보수 : 단일 파일에 모든 라우팅 로직이 포함되면 코드가 길어지고 복잡해지기 때문에, 코드를 이해하고 수정하기 어렵게 만들 수 있습니다.
- 확장성 : 프로젝트 규모가 커짐에 따라 더 많은 라우트와 관련된 로직이 추가 될 수 있습니다. 모든 라우트를 단일 파일에 넣으면 파일의 크기가 커지고, 관리가 어려워 질 수 있습니다.
- 모듈화와 재사용성 : 라우팅 로직이 모듈화되지 않으면 유사한 기능이 다른 엔드포인트에서 필요할 때 코드를 중복해서 작성해야 할 수 있습니다.
이러한 단점을 극복하기 위해 보다 구조화된 방식을 사용할 수 있습니다 (^∇^)
- 라우트 분리: `/users`, `/books` ... 와 같이 관련된 엔드포인트를 별도의 파일로 분리하기
- 모듈화 : 분리된 파일에서 관련 함수들을 작성하고 필요한 경우 해당 함수를 재사용할 수 있도록 구성하기
- MVC 구조 도입: 모델, 뷰, 컨트롤러와 같은 구조를 도입하여 코드를 더 관리하기 쉽게 만들기 (컨트롤러 사용하기)
하나의 예를 들어볼게요, 기존에 작성했던 routes 폴더 안의 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()
}
res.status(StatusCodes.CREATED).json(results)
}
);
});
이렇게 로직 + 라우터 역할을 다 하고 있었는데요
users.js 파일을 이렇게 수정하고,
const express = require('express'); // express 모듈
const router = express.Router();
const conn = require('../mariadb') // db 모듈
const join = require('../controller/userController');
router.use(express.json());
// 회원가입
router.post('/join', join);
controller를 만들어서
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()
}
res.status(StatusCodes.CREATED).json(results)
});
};
module.exports = join
그 안에 로직을 구현하면!
이제 라우터는 라우터의 역할만을 하고 있고, 콜백함수 자리에 join 모듈을 넣어 그 다음 역할은 userController.js로 넘기고 있어요 ㅎㅎ
혼자서 하나 더! 해보기
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();
}
})
};
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);
}
})
};
위 코드는 비밀번호 초기화 요청과 비밀번호 초기화 인데요, 비밀번호를 초기화 할 때,
새로 입력한 비밀번호가 기존 비밀번호와 똑같다면 오류 메세지를 보여주도록 한번 짜 볼게요 !!
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();
}
})
};
const passwordReset = (req, res) => {
const {email, password} = req.body;
// 이전 비밀번호 가져오기
let sql = `SELECT password FROM users WHERE email = ?`;
conn.query(sql, email,
(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();
}
const beforePassword = results[0].password;
if (beforePassword == password) {
return res.status(StatusCodes.BAD_REQUEST).json({
message : "비밀번호가 이전과 동일합니다. 다른 비밀번호를 입력해주세요."
})
} else {
// 새 비밀번호로 업데이트
let updateSql = `UPDATE users SET password = ? WHERE email = ?`;
let values = [password, email];
conn.query(updateSql, values,
(updateErr, updateResults) => {
if (updateErr) {
console.log(updateErr);
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(updateResults);
}
})
}
})
};
기존의 비밀번호인 1111을 그대로 입력했더니 ㅎㅎ 원하던 결과가 나왔습니다 !
변경도 잘 된걸 확인할 수 있어요
여기서 변경되기 이전의 비밀번호를 받아와야 하는데 처음 몇 번 이미 변경된 비밀번호를 받아오게끔 로직을 잘못 짜서
삽질을 또 조금(?) 했습니다 ㅎㅎ
그래도 또 하나 배웠네요 !
Crypto 모듈 사용해서 암호화를 해보자
'crypto' 모듈은 Node.js에서 제공하는 내장 모듈 중 하나로, 다양한 암호화 기능을 제공합니다!
주로 데이터의 보안을 위해 해시(hashing), 암호화(encryption), 복호화(decryption), 서명(signing), 그리고 무작위 바이트 생성
(random bytes generation) 등의 기능을 제공합니다 (ؑᵒᵕؑ̇ᵒ)◞✧
또한,, 복호화는 되지 않습니다 (단방향 ㅎㅎ;;)
// 비밀번호 암호화
const salt = crypto.randomBytes(64).toString('base64');
const hashPassword = crypto.pbkdf2Sync(password, salt, 10000, 64, 'sha512').toString('base64');
요 코드는 비밀번호를 안전하게 저장하기 위해 해시 함수를 사용하여 주어진 비밀번호를 암호화 하고, 이를 안전한 방법으로
저장할 수 있도록 하는 예시입니다 ㅎㅎ
'pbkdf2Sync()' 메서드는 비밀번호 기반 키 도출 함수로, 주어진 비밀번호를 해시화 하여 저장될 때 보다 안전하게 만들어 줍니다 !
// 비밀번호 암호화
const salt = crypto.randomBytes(10).toString('base64');
const hashPassword = crypto.pbkdf2Sync(password, salt, 10000, 10, 'sha512').toString('base64');
console.log(hashPassword);
이렇게 암호화 된 문자의 길이를 정해 줄 수도 있고, 암호화 된 값은 계속해서 바뀝니다 (⌒◞⌒)
따라서, 어떻게 암호화 된 비밀번호를 구분할것이냐 ... 하면
회원가입을 할 때 비밀번호를 암호화해서 암호화 된 비밀번호와, salt값을 같이 db에 저장한다
로그인 시, 이메일과 비밀번호(암호화상태가 아닌)를 받을텐데 => salt값을 꺼내서 비밀번호를 암호화 해보고
=> db에 저장된 비밀번호와 비교해서 맞는지 확인한다
const join = (req, res) => {
const {email, password} = req.body;
let sql = `INSERT INTO users(email, password, salt) VALUES(?, ?, ?)`
// 회원가입 시 비밀번호를 암호화해서 암호화된 비밀번호와, salt값을 같이 db에 저장
const salt = crypto.randomBytes(10).toString('base64');
const hashPassword = crypto.pbkdf2Sync(password, salt, 10000, 10, 'sha512').toString('base64');
console.log(hashPassword);
// 로그인 시, 이메일 & 비밀번호 (날것의)를 받을텐데, => salt 값 꺼내서 비밀번호를 암호화 해보고
// => 디비에 저장된 비밀번호와 비교해서 맞는지
let values = [email, hashPassword, salt];
conn.query(sql, values,
(err, results) => {
if(err){
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
res.status(StatusCodes.CREATED).json(results)
});
};
ㅎㅎ 한 80퍼센트만 이해한 상태에서 혼자 비밀번호가 같은 경우 코드짜기 + 거기에 암호화 더하기
를 하니까 한 반절만 이해한 사람이 되어버렸습니다... 해시 부분은 무조건 더 공부해야 할 것 같아요!!!
암호화가 된 상태에서 같은 비밀번호를 입력하면 오류가 발생하지 않기 때문에 ... ㅋ훔
'데브코스 TIL' 카테고리의 다른 글
[웹 풀사이클 데브코스 TIL] - 8주차 Day 3 - SQL 함수의 활용, 데이터베이스 페이징 (1) | 2024.01.04 |
---|---|
[웹 풀사이클 데브코스] 8주차 Day 2 - 쿼리스트링을 사용해 하나의 엔드포인트로 기능 분리하기 (0) | 2024.01.03 |
[웹 풀사이클 데브코스] 7주차 Day 5 - API 점검, Express-generator (2) | 2023.12.29 |
[웹 풀사이클 데브코스] 7주차 Day 4 - 도서 정보 API 설계 및 구현 (1) | 2023.12.28 |
[웹 풀사이클 데브코스] 7주차 Day 2 - 면접 관련 질문 정리 (0) | 2023.12.26 |