1️⃣ 회원가입 API를 구현해보자
// mysql 모듈 소환
const mariadb = require('mysql2');
//db와 연결통로 생성
const connection = mariadb.createConnection({
host : 'localhost',
user : 'root',
password : 'rood',
database : 'Bookshop',
dataString : true
});
module.exports = connection;
const express = require("express");
const router = express.Router();
const conn = require('../mariadb');
router.use(express.json()); // POST를 사용하면 값을 json형태로 받아오기 때문에 추가
// 회원가입
router.post('/join', (req,res) => {
const {email, password} = req.body;
let sql = 'INSERT INTO users (email, password) VALUES (?, ?)'
let values = [eamil, password]
conn.query(sql, values,
(err, results) => {
if(err) {
console.log(err);
return res.status(400).end // BAD REQUEST
}
res.status(201).json(results)
})
res.json('회원가입');
});
1️⃣ status를 더 정확하게 표현해보자

const express = require("express"); // express 모듈
const router = express.Router();
const conn = require('../mariadb'); // db 모듈
const {StatusCodes} = require('http-status-codes'); // status 모듈
router.use(express.json()); // POST를 사용하면 값을 json형태로 받아오기 때문에 추가
// 회원가입
router.post('/join', (req,res) => {
const {email, password} = req.body;
let sql = 'INSERT INTO users (email, password) VALUES (?, ?)'
let values = [eamil, password]
conn.query(sql, values,
(err, results) => {
if(err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end // BAD REQUEST
}
res.status(StatusCodes.CREATED).json(results)
})
res.json('회원가입');
});

1️⃣ node.js 패키지(파일) 구조
경로를 찾은 다음 역할 = '콜백함수'를 빼내자!
2️⃣ 컨트롤러란?
👉 router를 통해 "사용자의 요청(req)이" 길(url)을 찾아오면
👉 매니저(콜백함수 = controller)가 환영해줄 것임
👉 서비스에게 일을 시키고, 결과물을 매니저에게 전달 해주게 됨
👉 매니저(controller)가 사용자에게 res를 돌려줌
3️⃣ 컨트롤러를 만들어보자
users.js에서 콜백함수를 빼서 컨트롤러를 따로 만들어주자

만들어준 UserController.js
const {StatusCodes} = require('http-status-codes');
const conn = require('../mariadb'); // db 모듈
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 // BAD REQUEST
}
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 join = require('../controller/UserController'); // 컨트롤러 불러옴
router.use(express.json()); // POST를 사용하면 값을 json형태로 받아오기 때문에 추가
// 회원가입
router.post('/join', join)
1️⃣ controller를 정리해보자
const {StatusCodes} = require('http-status-codes'); // 상태 모듈
const conn = require('../mariadb'); // db 모듈
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 // BAD REQUEST
}
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
};
const express = require("express"); // express 모듈
const router = express.Router();
const conn = require('../mariadb'); // db 모듈
const {
join,
login,
passwordResetRequest,
passwordReset
} = require('../controller/UserController'); // 컨트롤러 불러옴
router.use(express.json()); // POST를 사용하면 값을 json형태로 받아오기 때문에 추가
router.post('/join', join) // 회원가입
router.post('/login', login) // 로그인
router.post('/reset', passwordResetRequest); // 비밀번호 초기화 요청
router.put('/reset', passwordReset); // 비밀번호 초기화
module.exports = router;
2️⃣ 로그인 API를 만들어보자
const login = (req, res) => {
const { email, password } = req.body; // 요청 본문에서 이메일과 비밀번호 추출
let sql = 'SELECT * FROM users WHERE email = ?'; // 이메일을 기준으로 사용자를 조회하는 SQL 쿼리
conn.query(sql, email, (err, results) => { // 데이터베이스 쿼리 실행
if (err) { // 쿼리 실행 중 에러가 발생한 경우
console.log(err); // 에러를 콘솔에 출력
return res.status(StatusCodes.BAD_REQUEST).end(); // 클라이언트에 400 Bad Request 응답
}
const loginUser = results[0]; // 조회된 첫 번째 사용자 정보를 가져옴
if (loginUser && loginUser.password == password) { // 사용자가 존재하고 비밀번호가 일치하는지 확인
// 로그인 성공
// JWT 토큰 발행
const token = jwt.sign({
email: loginUser.email // 토큰에 포함할 사용자 정보 (이메일)
}, process.env.PRIVATE_KEY, { // 비밀 키를 이용해 토큰을 서명
expiresIn: '5m', // 토큰 만료 시간 설정 (5분)
issuer: 'kim' // 토큰 발행자 정보
});
// JWT 토큰을 쿠키에 담아서 클라이언트에게 전달
res.cookie("token", token, {
httpOnly: true // 쿠키는 클라이언트의 JavaScript에서 접근할 수 없도록 설정 (보안 강화)
});
console.log(token); // 생성된 토큰을 콘솔에 출력
return res.status(StatusCodes.OK).json(results); // 로그인 성공 시 200 OK와 함께 결과 응답
} else {
// 로그인 실패 (사용자가 없거나 비밀번호가 틀린 경우)
return res.status(StatusCodes.UNAUTHORIZED).end(); // 401 Unauthorized 응답
}
});
};
1️⃣ 비밀번호 초기화 요청 API 만들기
// 비밀번호 초기화 요청 함수 정의
const passwordResetRequest = (req, res) => {
// 클라이언트가 보낸 요청 본문에서 이메일을 추출
const { email } = req.body;
// 이메일을 기준으로 사용자를 조회하는 SQL 쿼리문 작성
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(); // 클라이언트에 400 Bad Request 응답
}
// 쿼리 결과에서 첫 번째 사용자 정보를 가져옴
const user = results[0];
// 사용자가 존재하는 경우
if (user) {
return res.status(StatusCodes.OK).json({
email: email // 성공적으로 조회된 이메일을 JSON으로 응답
});
} else {
// 사용자가 존재하지 않는 경우
return res.status(StatusCodes.UNAUTHORIZED).end(); // 401 Unauthorized 응답
}
});
};
2️⃣ 비밀번호 초기화API 만들기

// 비밀번호 초기화 함수 정의
const passwordReset = (req, res) => {
// 클라이언트가 보낸 요청 본문에서 email과 password를 추출
const { email, password } = req.body;
// 비밀번호를 업데이트하는 SQL 쿼리문 정의
let sql = 'UPDATE users SET password = ? WHERE email = ?';
// 쿼리에 바인딩할 값들 (password와 email)
let values = [password, email];
// 데이터베이스에 쿼리 실행
conn.query(sql, values, (err, results) => {
// 쿼리 실행 중 오류가 발생한 경우
if (err) {
console.log(err); // 에러를 콘솔에 출력
return res.status(StatusCodes.BAD_REQUEST).end(); // 클라이언트에 400 Bad Request 응답
}
// 쿼리가 성공적으로 실행되었으나, affectedRows가 0인 경우 (해당 이메일이 존재하지 않는 경우)
if (results.affectedRows == 0)
return res.status(StatusCodes.BAD_REQUEST).end(); // 400 Bad Request 응답 (해당 이메일을 가진 사용자가 없음)
else
// 성공적으로 비밀번호가 변경된 경우
return res.status(StatusCodes.OK).json(results); // 200 OK 응답과 함께 결과 반환
});
};
1️⃣ 비밀번호가 날 것으로 들어가면 안됨
데이테 베이스를 보면 비밀번호를 그대로 저장해둠 이렇게 저장하면 털리면 끝...

패스워드 암호화 모듈을 배워보자!
2️⃣ 회원가입에 비밀번호 암호화 추가
// 비밀번호 암호화
const salt = crypto.randomBytes(64).toString('base64');
const hashPassword = crypto.pbkdf25ync(password, salt, 10000, 64, 'sha512').toString('base64')
3️⃣ node-base에서 테스트 해보자
const crypto = require('crypto');
const password = "1111";
// 비밀번호 암호화
const salt = crypto.randomBytes(64).toString('base64');
const hashPassword = crypto.pbkdf2Sync(password, salt, 10000, 64, 'sha512').toString('base64')
console.log(hashPassword);

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);
4️⃣ 암호화의 복구 방법
👉 회원가입 시 비밀번호를 암호화 해서 암호화된 비밀번호와 salt 값을 같이 저장한다.
👉 로그인 시 이메일 & 비밀번호(날 것)를 받아서 => DB에서 slat값 꺼내서 비밀번호 암호화 해보고 => DB에 저장된 비밀번호랑 비교한다.
먼저, 워크벤치에서 users 테이블에 salt를 추가해준다.

암호화된 비밀번호와 slat값을 DB에 함께 저장하도록 코드 수정
const {StatusCodes} = require('http-status-codes'); // 상태 모듈
const conn = require('../mariadb'); // db 모듈
const jwt = require('jsonwebtoken'); // jwt 모듈
const crypto = require('crypto') // 암호화 모듈
const dotenv = require('dotenv'); // env모듈
dotenv.config(); // 사용 선언
const join = (req,res) => {
const {email, password} = req.body;
let sql = 'INSERT INTO users (email, password, salt) VALUES (?, ?, ?)';
// 암호화된 비밀번호와 slat값을 DB에 함께 저장함
const salt = crypto.randomBytes(10).toString('base64');
const hashPassword = crypto.pbkdf2Sync(password, salt, 10000, 10, 'sha512').toString('base64')
// 로그인 시 이메일 & 비밀번호(날 것)를 받아서 => DB에서 slat값 꺼내서 비밀번호 암호화 해보고 => DB에 저장된 비밀번호랑 비교한다.
let values = [email, hashPassword, salt]; // 이메일과 해시패스워드, salt로 insert 해줌
conn.query(sql, values,
(err, results) => {
if(err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end // BAD REQUEST
}
return res.status(StatusCodes.CREATED).json(results)
})
};

1️⃣ 로그인 시에도 암호화 적용
// 로그인
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(); // BAD REQUEST
}
const loginUser = results[0];
// DB에서 slat값 꺼내서 날 것으로 들어온 비밀번호를 암호화 해보고
const hashPassword = crypto.pbkdf2Sync(password, loginUser.salt, 10000, 10, 'sha512').toString('base64')
if (loginUser && loginUser.password == hashPassword) { // DB에 저장된 비밀번호랑 비교한다.
// 토큰 발행
const token = jwt.sign({
email : loginUser.email
}, process.env.PRIVATE_KEY, {
expiresIn : '5m',
issuer : 'kim'
});
//토큰 쿠키에 담기 = jwt 토큰을 바디로 보내지 않고 쿠키로 보냄
res.cookie("token", token, {
httpOnly : true
});
console.log(token);
return res.status(StatusCodes.OK).json(results)
} else {
return res.status(StatusCodes.UNAUTHORIZED).end(); // 401 : Unauthorized(미인증 상태), 403 : Forbidden(접근권한 없음)
}
});
};

2️⃣ 비밀번호 초기화 로직에도 암호화를 적용해보자
// 비밀번호 초기화
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(); // BAD REQUEST
}
if(results.affectedRows == 0)
return res.status(StatusCodes.BAD_REQUEST).end();
else
return res.status(StatusCodes.OK).json(results);
}
)
};