프로젝트를 진행하면서 생성한 API와 로직에 대해 설명을 하겠다.
const crypto = require("crypto");
// 솔트 생성
const createSalt = () => {
return new Promise((resolve, reject) => {
crypto.randomBytes(64, (err, buf) => {
if (err) reject(err);
resolve(buf.toString("base64"));
});
});
};
// 암호비번 생성
const createCryptoPassword = async (plainPassword) => {
console.log(">>>>>>>>>>", plainPassword);
const salt = await createSalt();
return new Promise((resolve, reject) => {
crypto.pbkdf2(plainPassword, salt, 10000, 64, "sha512", (err, key) => {
if (err) {
console.log("createCryptoPassword 에서 에러 발생");
reject(err);
}
resolve({ password: key.toString("base64"), salt });
});
});
};
// 비밀번호 검증
const getCryptoPassword = (plainPassword, salt) => {
return new Promise((resolve, reject) => {
crypto.pbkdf2(plainPassword, salt, 10000, 64, "sha512", (err, key) => {
if (err) {
console.log("getCryptoPassword 에서 에러 발생");
reject(err);
}
resolve({ password: key.toString("base64"), salt });
});
});
};
module.exports = {createSalt, createCryptoPassword, getCryptoPassword}
사용자가 전달한 암호는 createCryptoPassword
에서 createSalt
로 생성한 Salt값
을 활용하여 같이 sha-512 해쉬 알고리즘
으로 해쉬화
가 된다.
getCryptoPassword
는 사용자가 입력한 암호와 DB에 저장되어 있는 Salt값을 이용하여 위와 동일하게 해쉬화 한다.
1) 렌더링(5개)
exports.RenderMain = (req,res) =>{
res.render('main')
}
// 로그인 페이지
exports.RenderLogin = (req, res) => {
res.render("login");
};
// 회원가입 페이지
exports.RenderSignup = (req, res) => {
res.render("signup");
};
// 방 생성 페이지
exports.RenderMakeRoom = (req,res)=>{
res.render('makeRoom')
}
// 채팅 페이지
exports.RenderChat = (req, res) => {
res.render("chatRoom");
};
router.get('/makeRoom',logined,controller_render.RenderMakeRoom)
router.get('/chat',controller_render.RenderChat)
여기서 방 생성 페이지
와 채팅 페이지
는 passport
를 사용하여 로그인
을 해야만 접근이 가능하게 해주었다.
// 메인페이지(초기 데이터 삽입)
exports.GetData = async (req,res) =>{
const result = await Room.find({})
res.send({ data: result })
}
MVC
로 분리하기 전에는 mongodb
를 사용하였는데 mongoose
를 사용하니 findAll
메소드를 사용할 수 없다
는 것을 알게 되었고 find({})
를 하면 전체 데이터
를 불러올 수 있다는 사실을 알게 되었다.
// 방 정보가 담긴 예약 페이지
exports.DetailPage = async (req, res) => {
let getId = req.params.id;
console.log("getId:", getId);
await Room.findOne({ _id: ObjectId(getId)}).then((result)=>{
console.log("방정보", res);
res.render("DetailedPage", { data: result});
})
};
여기서 id
는 DB에 저장된 방에 대한 id
인데 RESTful적
으로 id
보다는 room-number
가 더 좋은 것 같다.
Front
측에서 전달한 params(id)
와 DB
에 저장된 Rooom 스키마
의 ID
를 비교하여 같은 ID를 가진 데이터
를 data
라는 이름의 객체
로 전달하면서 DetailedPage
를 렌더링
해준다.
// 로그아웃
exports.Logout = (req,res,next)=>{
req.logout((err)=>{
if(err){
return next(err)
}
else{
req.session.destroy(()=>{
res.clearCookie('connect.sid')
res.redirect('/main')
})
}
})
}
passport
에서 로그인에 성공
하면 세션이 생성
되고 브라우저
에는 connet.sid
라는 이름의 쿠키가 생성
이 된다. 그렇기 때문에 로그아웃 버튼
을 누르게 되면 /logout
하여 세션을 지우고
또한 쿠키도 제거
하여 메인 페이지로 이동
하게 하였다.
- 로그인 전
- 로그인 후
- 쿠키 생성
// 회원가입
exports.Signup = async (req, res) => {
const r = req.body;
console.log(r)
let cryptoPassword = await createCryptoPassword(r.pw);
const result = await User_Info.find({id : r.id})
console.log('res!: ',result)
if(result.length===0){
User_Info.create(
{
name: r.name,
birthday: r.birthday,
email: r.id,
pw: cryptoPassword.password,
salt: cryptoPassword.salt,
gender: r.gender,
}).then((re)=>{
console.log('유저정보 저장 완료')
}).catch((err)=>{
console.log(err)
})
res.redirect("/login");
}
else{
console.log("중복자 발견");
res.send("<script>location.href='/signup'; alert('ID가 중복되었어요!');</script>");
}
};
Front
에서 전달받은 값
들은 req.body
에 담기게 된다.
거기서 pw(비밀번호)
를 1번에 있는 createCryptoPassword 함수
를 이용하여 비밀번호 평문
을 해쉬화
한다. 그 다음 사용자가 전달한 id(email)
의 중복 여부를 체크
하고 만약 중복자가 없다면
result
는 빈 배열을 반환
한다.
그렇기 때문에 .length
를 사용하면 길이가 0
이 되어 전달받은 값
과 해쉬화된 비밀번호
를 DB에 저장
하고 login 페이지로 이동
한다.
만약 중복자가 있으면
id가 중복되었다는 메시지
를 Front
에게 보여준다.
// 방 생성
exports.MakeRoom = async (req,res)=>{
const r = req.body
console.log(r)
let img_src;
switch (r.category) {
case "축구":
img_src = "../static/img/soccer.jpg";
break;
case "야구":
img_src = "../static/img/baseball.jpeg";
break;
case "농구":
img_src = "../static/img/basketball.jpeg";
break;
case "배구":
img_src = "../static/img/volleyball.jpeg";
default:
break;
}
if(r.title=='' || r.date=='' || r.location=='' || r.personnel=='' || r.price=='' || r.category=='') {
res.send("<script>location.href='/makeRoom'; alert('제대로 입력하세요!');</script>");
}
else{
await Room.create({
src: img_src,
title: r.title,
date: r.date,
location: r.location,
personnel: r.personnel,
price: r.price,
category: r.category,
}).then((result)=>{
console.log('방정보 저장')
res.redirect('/main')
}).catch((error)=>{
console.log(error)
})
}
}
Front
에서 전달받은 값
들은 req.body.category
를 보고 각 스포츠에 맞는 이미지 주소
를 img_src 변수
에 넣는다.
그 다음 하나라도 빈값이 있으면
다시 입력하라는 메시지
와 makeRoom(예약 생성)페이지로 이동
시킨다. (Front에서 required를 사용하여 방지할 수 있을 것 같다.)
만약 제대로 다 입력
을 하였다면 req.body의 정보들
을 DB의 Room 스키마에 저장
하게 된다.
exports.Login = (req,res,next)=>{
passport.authenticate('local',(err,user,info)=>{
if(err){
console.error(err)
return next(err)
}
if(!user){
return res.send("<script>location.href='/login'; alert('비밀번호가 일치하지 않습니다!');</script>");
}
return req.login(user,(loginError)=>{
if(loginError){
console.log(loginError)
return next(loginError)
}
return res.redirect('/main')
})
})(req,res,next)
}
Strategy 종류 (로그인 인증 방식)
1) Local Strategy(passport-local) : 로컬 DB에서 로그인 인증 방식
2) Social Authentication (passport-kakao, passport-twitter 등) : 소셜 네트워크 로그인 인증 방식
여기서는 Local Strategy를 사용하였다.
로그인 요청이 들어오면 passport.authenticate() 호출한다.
이때 authenticate는 passport/localStrategy.js 호출한다.module.exports = ()=>{ passport.use(new LocalStrategy({ usernameField : 'id', passwordField : 'pw', }, async(user_id,user_pw,done)=>{ try{ let result = await User_Info.findOne({email:user_id}) if(result){ const getcry = await getCryptoPassword(user_pw,result.salt) if(getcry.password == result.pw){ done(null,result) } else{ done(null,false,{message:'wrong password'}) } } else{ done(null,false,{message:'not exist'}) } } catch(err){ console.error(err) done(err) } } )) }
여기서 요청된
id와 pw
는user_id, user_pw
가 되고DB에 저장된
email과 user_id를 비교
하여데이터가 있다면 result에
담기고없다면 done(null,false,{message:'not exist'})
해준다.
result
에 담긴salt
와전달받은 user_pw
를getCryptoPassword
를 이용하여 얻은해쉬화된 값
과DB에 저장된 해쉬화된 비밀번호
를 비교하여같다면 done(null,result)
,틀리다면 done(null,false,{message:'wrong password'})
해준다.
로그인을 실행
하고,done()을 호출
하면, 다시passport.authenticate() 돌아가서 다음 미들웨어를 실행
한다.
=> (err,user,info) 부분
done()정보를 토대
로,로그인 성공 시 사용자 정보 객체와 함께 req.login()를 자동으로 호출
한다.module.exports = ()=>{ // id를 이용해 세션을 저장(로그인 성공시 발동) passport.serializeUser((user, done)=>{ done(null, user.id); }); passport.deserializeUser((id,done)=>{ User_Info.findOne({_id : ObjectId(id)}) .then((user) => { done(null,user) }) .catch(err=>done(err)) }) local() };
1)
req.login 메서드
가passport.serializeUser() 호출
한다.
2)req.session
에사용자 아이디 키값 저장
3)passport.deserializeUser()
로 바로 넘어가서DB 조회후
req.user 객체를 등록
하고done() 반환
하여req.login 미들웨어로 다시 되돌아간다.
4) 미들웨어 처리후,res.redirect('/main')을 응답
하면,세션쿠키를 브라우저에 보내게 된다.
5) 로그인 완료 처리 (이제세션쿠키를 통해서 통신
하며 로그인됨 상태를 알 수 있다.)
로그인 이후
- 모든 요청에 passport.session() 미들웨어가 passport.deserializeUser() 메서드를 매번 호출한다.
// 세션 및 쿠키 세팅 app.use( session({ resave: false, saveUninitialized:false, secret:process.env.COOKIE_SECRET, cookie: { httpOnly : false, } }) ) app.use(passport.session())
- deserializeUser에서 req.session에 저장된 아이디로 DB에서 사용자 조회
module.exports = ()=>{ // id를 이용해 세션을 저장(로그인 성공시 발동) passport.serializeUser((user, done)=>{ done(null, user.id); }); passport.deserializeUser((id,done)=>{ User_Info.findOne({_id : ObjectId(id)}) .then((user) => { done(null,user) }) .catch(err=>done(err)) }) local() };
조회된 사용자 전체 정보
를req.user 객체에 저장한다.
- 그 후
라우터에서 req.user를 공용적으로 사용 가능
하게 된다.
로그인 이후에만 접근
exports.logined = (req, res, next)=>{ console.log(req.user) if (req.user) { next(); }else{ res.send("<script>location.href='/main'; alert('로그인하세요')</script>"); }
공용적으로 사용 가능 해진
req.user를 이용
하여req.user가 존재
하면next()
하여다음 과정이 진행
되지만그렇지 않을 경우 로그인하라는 alert
가 나온다.const {logined} = require('../routes/Auth') router.get('/makeRoom',logined,controller_render.RenderMakeRoom) router.get('/DetailedPage/:id',logined,controller_room.DetailPage) router.post('/make/room',logined,controller_room.MakeRoom)
logined 함수
를 불러와라우터의 미들웨어로 넣을 경우
로그인 한 사람만
해당 라우터에 접근이 가능
하다.