Node.js 교과서'라는 책을 통하여 공부.
22.01.19(수)까지 공부한 내용을 기록.
~ 428p
내가 공부하는 책에서 프론트의 전반적인 코드는 다 제공 됐고, 백엔드 서버에 관해서만 다룬다. 그래서 프론트 코드는 따로 블로그에 올릴 생각은 없다. 그리고 여기 코드를 이렇게 올리면 되나 싶은데, 책 저자분의 깃허브에가면 모든 실습코드가 다 공개 되고 있다 그러니 괜찮을 것 같다! 익스프레스를 공부하면 공부할 수록, 이게 뭔가 자율성이 너무 많고 딱딱 체계화 된거 같지가 않은 느낌이 든다.. 그래서 그런지 헷갈리기도 하고... 장고공부하는 것도 포스팅 할 생각인데 장고는 엄청 체계적인 느낌이 든다. 그래도 칼은 뽑았으니까 이책은 완독해볼 생각이다.
이번에는 로그인처리 하는방법에 대해서 공부했는데, Passport모듈이라는 것을 사용했다. 사실 로그인이라는 것이 쉽다면 엄청 쉽게 구현 할 수 있지만, 그만큼 보안이 취약해지기 때문에 평범한 개발자가 아마 로그인 기능을 0부터 구현하라고 한다면 상당히 보안적으로 구멍이 많을 것이다. 그래서 Passport라는 모듈을 많이 이용한다고 한다. 해당 모듈을 로컬로그인 부터, SNS(카카오톡)로그인까지 지원한다고 한다. 대신에 이런 모듈을 사용할때는 완전 이해하려고 하기보다는 어느정도는 수긍하면서 사용법을 외우면서 사용하는 것이 맞다고 한다...ㅠ
코드를 다 적지는 않을 것이다.. 너무 많기도 하고 그냥 복붙 하는거랑 다를게 없는거 같아서..
#Passport 모듈 설치
npm i passport passport-local passport-kakao bcrypt
npm을 이용해서 위 모듈을 설치해주는데, bcrypt는 보안을 위해 사용한다.
#local로그인 구현하기
##app.js index.js
//app.js
const passport = require('passport'); /
/모듈을 불러와 준다.
//...
//다른거 임포트 해주기
//...
const passportConfig = require('./passport'); //passport 폴더안의 index.js으로 자동으로 지정된다.
const app = express();
passportConfig();// passport안 index.js가 실행된다. index.js는 내가 따로 만들어 줘야 한다!
//...
//...
app.use(passport.initialize()); //req에 passport 설정을 심어줌
app.use(passport.session()); //req에 req.session 객체에 passport 정보를 저장
app.use('/', pageRouter)
이제 passport가 가져온 index.js를 만들어 보도록 한다.
// passport/index.js
const passport = require('passport');
const local = require('./localStrategy');
//const kakao = require('./kakaoStrategy'); 카카오 로그인 구현할때 쓰임
const User = require('../models/user');
module.exports = () => {
passport.serializeUser((user,done)=>{
done(null,user.id);
});
passport.deserializeUser((id,done)=>{
User.findOne({where : {id}})
.then((user)=>{
console.log("find id!")
return done(null, user)
})
.catch(err=>done(err));
});
local();
//kakao(); 카카오 로그인 구현할때 쓰임
}
serializeUser는 로그인할때 실행된다고 한다. req.session에 어떤 데이터를 저장할 지 정하는 메서드이다. user에는 사용자 정보가 들어있는데 그걸 done()함수를 이용해서 deserializeUser에 id로 보내준다. deserializeUser는 매 요청시마다 실행되는데, 세션에 저장했던 아이디를 받아 데이터베이스에서 사용자 정보를 조회한다. 그리고 그 정보를 req.user에 저장한다. 그래서 앞으로 req.user에서 로그인한 사용자 정보를 가져 올 수 있다.
serializeUser ===> 사용자 정보 객체를 세션에 아이디(user.id)로 저장하는것.
deserializeUser ===> 세션에 저장한 아이디를 통해 사용자 정보 객체를 불러오는 것.
##미들웨어 만들어주기
미들웨어를 하나 만들어 줄 것이다. 이 미들웨어는 로그인상태인지 아닌지를 판별해준다.
//middleware.js
exports.isLoggedIn = (req,res,next) => {
if(req.isAuthenticated()) {
next()
}else{
res.status(403).send('로그인 필요');
}
}
exports.isNotLoggedIn = (req, res, next) => {
if (!req.isAuthenticated()) {
next()
} else {
const message = encodeURIComponent('로그인한 상태입니다.');
res.redirect(`/?error=${message}`);
}
}
req.isAuthenticated()을 사용해서 로그인여부를 판별한다. isAuthenticated()이 함수는 Passport가 req 객체에 추가해줬다! 이런 것 때문에 외울게 좀 있고 그냥 사용법이라고 생각해야 하는 것 같다. else면 next()를 하지 않는다. 그러면 다음 미들웨어로 넘어가지 않게된다!
##만든 미들웨어 사용하기
// routes/page.js 전체 코드는 아니다!
const express = require('express');
const {isLoggedIn, isNotLoggedIn} = require('./middlewares');
const router = express.Router();
router.get('/profile',isLoggedIn,(req,res)=> {
res.render('profile', {title : '내 정보 - NodeBird'});
});
router.get('/join',isNotLoggedIn,(req,res)=>{
res.render('join', {title : '회원가입 - NodeBird'});
});
module.exports = router;
이렇게 미들웨어를 불러와주고, router.get()에 미들웨어로 넣어주면 로그인 상태인지 판별을 하게 되는데 만약 정상적으로 통과한다면 그 다음 미들웨어로 이어지며 res.render()를 실행 할 것이고 아니라면 next()를 주지 않았기 때문에 실행되지 않는 것을 이용한 것이다.
##회원가입 로그인 로그아웃 라우터 만들기
// routes/auth.js
const express = require('express');
const passport = require('passport');
const bcrypt = require('bcrypt');
const { isLoggedIn, isNotLoggedIn} = require('./middlewares');
const User = require('../models/user');
const router = express.Router();
router.post('/join',isNotLoggedIn, async (req,res,next)=>{
const {email, nick, password} = req.body;
try {
const exUser = await User.findOne({ where : {email}});
if(exUser) {
return res.redirect('/join?error=exist');
}else {
const hash = await bcrypt.hash(password,12);
await User.create({
email,
nick,
password : hash,
});
return res.redirect('/');
}
}catch(error){
console.error(error);
return next(error);
}
});
router.post('/login',isNotLoggedIn,(req,res,next)=>{
passport.authenticate('local',(authError,user,info)=>{
if(authError){
console.error(authError);
return next(authError);
}
if (!user){
return res.redirect(`/?loginError=${info.message}`)
}
return req.login(user, (loginError) => {
if(loginError) {
console.error(loginError);
return next(loginError);
}
return res.redirect('/');
});
})(req,res,next);
});
router.get('/logout',isLoggedIn,(req,res)=>{
req.logout();
req.session.destroy();
res.redirect('/');
});
module.exports = router;
이 js파일은 우리가 (서버주소)/auth/join 또는 (서버주소)/auth/login을 하게되면 오는 곳이다.
join은 회원가입이다. DB에 동일한 닉네임이 있는지 확인하고 있다면 가입을 막고, 없다면 비밀번호를 hash화 시켜서 DB에 저장한다.
난 로그인에서 조금 이해가 안갔었다. passport.authenticate('local', ... ) 이 있는데, 일단 여긴 로그인 버튼을 눌렀을 때라는 것을 생각하자. 저 코드를 만나면 passport/index.js에 local()을 등록해놨는데 그 local은 localStrategy을 불러온 것이다. 그래서 localStrategy로 이동한다. 이것이 성공적으로 실행됐다면, user객체가 올바르게 넘어오고, 그 user객체롤 통해서 req.login(user, (...)=> {})을 실행해서 로그인 시킨다. 이 login은 passport.serializeUser를 호출한다.
##로그인 전략
아까 이 localStrategy에서 성공하면 req.login을 호출하고 ... 뭐 이렇게 설명했는데 어떻게 되는건지 한번 봐보자.
//passport/localStrategy.js
const passport = require('passport');
const localStrategy = require('passport-local').Strategy;
const bcrypt = require('bcrypt');
const User = require('../models/user');
module.exports = () => {
passport.use(new localStrategy({
usernameField : 'email',
passwordField : 'password'
}, async(email,password,done)=>{
try {
const exUser = await User.findOne({where : {email}});
if(exUser) {
const result = await bcrypt.compare(password, exUser.password);
if(result) {
done(null,exUser);
}else {
done(null,false,{message:'비밀번호가 일치하지 않습니다.'});
}
} else {
done(null,false, {message:'가입되지 않은 회원입니다.'});
}
} catch (error) {
console.error(error);
done(error)
}
}
))
};
usernameField에는 req.body에 내가 준 속성명을 적어준다 나는 email을 줬었다. passwordField도 똑같이 password를 적어준다. 즉 req.body.email , req.body.password의 값들을 가져온다는 것이다. 그러면 그 값들이 async의 첫번째와 두번째 매개변수로 전해지면서 DB에 해당 이메일이 있는지 판단하고, 있다면 암호화된 비밀번호와 입력한 비밀번호를 비교해서 로그인 전략을 수행시킨다. 이렇게 여기서 성공여부를 그 다음으로 전해준다는 의미였다.
# 로직 정리
사용자가 로그인 창에 email,password를 입력한다. 그리고 로그인 버튼을 딱 누른다. 그 이후부터의 과정이다.
1. (주소)/auth/login 으로 로그인 요청이 들어온다.
2. passport.authenticate('local',()=>{..})가 호출되면서 로그인 전략으로 이동한다
3. 로그인 전략을 통해 DB와 비교한다.
4. 로그인 성공 시 사용자 정보 객체와 함께 req.login 을 호출한다
5. passport.serializeUser 호출한다.
6. req.session에 사용자 아이디만 저장한다
7. 로그인이 완료됐다.
-- 로그인 완료 --
8. 새로운 요청이 들어온다
9. passport.deserializeUser메서드 호출을 통해 로그인 된 사용자인지를 판단한다.
10. req.session에 저장된 아이디로 데이터베이스에서 사용자를 조회한다.
11. 조회된 사용자 정보를 req.user에 저장한다
12. 라우터에서 req.user객체가 사용가능해진다.
이게 사실 너무 복잡하기도하고, 기록용도로 글을 쓰다보니까 너무 세세하지 못한게 있어서 이해가 잘 안될 수도 있는데... 만약 다른사람이 내 블로그와서 이것만 보고 passport가 이해가 된다면 그건 말이 안된다.. 나도 책을 몇번이나 봐가면서 겨우 이해했지만 아직도 자세하게는 잘 모르겠다.. 아 그리고 아직 http에 대해서 잘 모르겠다. 인프런에 좋은강의가 있는데 5만5천원이라 고민된다 ㅠ
카카오 로그인전략도 있는데 그건 안올릴거다.. 아직 로그인전략도 내가 100%이해했다고 보기 어렵다. 책 끝내고 작게 스몰프로젝트 하면서 다시 로그인부분 정리를 꼭 해봐야겠다. ㅠ
'• 개발 > node.js' 카테고리의 다른 글
[node.js] 익스프레스(express) 사용하기, 미들웨어란, 미들웨어 순서 (0) | 2022.01.17 |
---|---|
[node.js] npm 기본개념 및 package.json 과 package-lock.json에 대해서 (0) | 2022.01.14 |
[node.js] http모듈을 이용해 서버를 시작해 보기, Cookie와 Session알아보기 (0) | 2022.01.12 |
[node.js] 노드 기본개념, JS(ES2015+)문법, 노드 기능 알아보기 (0) | 2022.01.09 |
[node.js] 공부 시작에 앞서 (0) | 2022.01.09 |