9시 24분
Passport.js 본문
앞서 진행했던 코드에 passport.js를 도입해보자. ( 배우기는 쉽지만 활용하기는 어렵대 ....! )
npm install -s passport
passport에서는 전략(Strategy)이라는 로그인 방식을 이용해서 oauth를 구현한다.
그리고 이번 수업에서는 아이디와 비밀번호를 이용하여 로그인하는 방식을 구현할 것이기 때문에 passport-local도 설치해준다. ( 페이스북 로그인 등을 사용할 시 다른 미들웨어를 설치한다 )
npm install -s passport-local
passport는 세션을 내부적으로 사용하기 때문에 session을 활성화하는 코드 아래에 넣어주어야 한다.
const passport = require('passport')
, LocalStrategy = require('passport-local')
.Strategy;
폼에서 데이터를 전송할 때 login_process로 데이터가 전송된다.
우리는 이 데이터가 전송되는 login_process 부분을 수정하여야 한다.
일단 아까 passport 모듈을 불러온 곳 아래에 다음 코드를 추가하자.
app.post('/auth/login_process',
passport.authenticate('local', {
successRedirect: '/',
failureRedirect: '/auth/login'
}));
- username과 password를 이용하는 local 방식을 사용
- 성공했을 땐 홈으로 리다이렉트하고,
- 실패했을 땐 로그인 화면으로 리다이렉트한다.
그 다음, passport가 어떻게 자격을 확인하는지 ( 아이디, 비밀번호 일치 여부 )에 대해 배워보자.
모듈을 불러온 곳 아래에 다음 코드를 추가하자.
passport.use(new LocalStrategy(
{
usernameField: 'id',
// passwordField: 'password'
},
function(username, password, done){
}
));
- usernameField와 passwordField를 이용하여 디폴트 이름인 'username' & 'passwrod'를 사용자 지정 이름으로 변경할 수 있다.
passport.use(new LocalStrategy(
{
usernameField: 'id',
// passwordField: 'password'
},
function(username, password, done){
if(username === authData.id){
if(password === authData.password){ // 로그인 성공
return done(null, authData);
} else{ // 비밀번호 다름
return done(null, false, {
message: 'Incorrect password'
});
}
} else{ // 아이디 다름
return done(null, false, {
message: 'Incorrect username'
});
}
}
));
- 기존에는 조건문으로 authData와 비교하여 사용자 확인
- done 메서드에서 false를 주지 않으면 true로 간주
passport 변수를 선언한 곳 아래 다음 코드를 추가해주자.
app.use(passport.initialize());
app.use(passport.session());
기존에는 우리가 직접 로그인을 구현했다. passport.initialize()와 passport.session() 미들웨어를 사용하여 passport를 통해 세션 미들웨어를 사용할 것이다.
passport.serializeUser(function(user, done){
console.log('serializeUser', user);
done(null, user.id);
});
passport.deserializeUser(function(id, done){
console.log('deserializeUser', id);
done(null, authData);
// User.findById(id, function(err, user){
// done(err, user);
// });
});
serializeUser() - 로그인에 성공했을 때 호출됨 ( 최초 )
우리가 로그인에 성공했을 때 authData를 리턴해주었다. 이 authData가 serializeUser 함수의 첫번째 인자로 전달되고, serializeUser()은 식별자를 세션 스토어에 저장
deserializeUser() - 로그인 성공 후 각각의 페이지를 방문할 때마다 콜백 함수를 호출함
데이터가 저장된 곳(authData)에서 데이터를 가져와서 필요한 정보를 조회할 때 사용한다.
< lib/auth.js >
module.exports = {
isLogin: function(request, response){ // 로그인 여부 확인
if(request.user){
return true;
} else{
return false;
}
},
statusUI: function(request, response){
var authStatusUI = '<a href="/auth/login">login</a>';
if(this.isLogin(request, response)){
authStatusUI = `${request.user.nickname} | <a href="/auth/logout">logout</a>`;
}
return authStatusUI;
}
}
- passport가 request에 user라는 객체를 추가시켜준다. ( deserializeUser 함수에서 authData를 가져와서 )
- if(request.user) = request에 user 객체가 있다면, undefined는 false와 같음
악 도대체 어디서 잘못된거야 !!!!!!!!!!!! 어디서 잘못된지 모르겠음 ㅠㅠ 영상 쭉 보면서 다시 해봐야할듯 ,,,
>> 해결한듯? 원래 세션이 수정 안되는 에러가 있었다. 저 아래 코드의 resave에 'false'라고 했길래 false로 고쳐준거밖에 특별히 한게 없는데 저거 때문에 에러 수정한건가 ?ㅅ?
app.use(session({
httpOnly: true,
secret: 'qwertyasdfg',
resave: false,
saveUninitialized: true,
store: new fileStore()
}));
로그아웃 관련 router.get() 함수를 아래와 같이 수정해주자.
router.get('/logout', function(request, response){
request.logout();
request.session.save(function(err){
response.redirect('/');
});
});
그럼 기본적인 로그인 / 로그아웃 기능을 구현하는데 성공했다!!
근데 로그인을 했을 때 새로고침을 해야지 로그인 UI가 작동한다. 콘솔에 출력을 해보니 로그인 성공 직후에는 request에 user 객체가 undefined하다고 뜬다 흠 어떻게 고치지 ㅠㅅㅠ
pm2 start main.js --watch --no-daemon --ignore-watch="/sessions/*"
- node가 재시작되어 세션 정보가 저장되지 않는 현상을 방지
교통정리를 위해 main.js, home.js, lib/auth.js, routes/auth.js 파일의 전체 코드를 정리한다.
< main.js >
const express = require('express');
const app = express();
// 미들웨어 불러오기
const compression = require('compression');
const helmet = require('helmet');
const session = require('express-session');
const fileStore = require('session-file-store')(session);
// 라우팅 불러오기
const homeRouter = require('./routes/home.js');
const topicRouter = require('./routes/topic.js');
const authorRouter = require('./routes/author.js');
const authRouter = require('./routes/auth.js');
// 미들웨이 사용
app.use(express.urlencoded({extended:false}));
app.use(compression());
app.use(express.static('public'));
app.use(helmet());
app.use(session({
httpOnly: true,
secret: 'qwertyasdfg',
resave: false,
saveUninitialized: true,
store: new fileStore()
}));
const authData = {
id:'leeeeeyeon',
password: '1',
nickname: 'leeeeeyeon'
}
const passport = require('passport')
, LocalStrategy = require('passport-local')
.Strategy;
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser(function(user, done){
console.log('serializeUser', user);
done(null, user.id);
});
passport.deserializeUser(function(user, done){
console.log('deserializeUser', user);
done(null, authData);
});
passport.use(new LocalStrategy(
{
usernameField: 'id'
// passwordField: 'password'
},
function(username, password, done){
if(username === authData.id){
if(password === authData.password){ // 로그인 성공
return done(null, authData);
} else{ // 비밀번호 다름
return done(null, false, {
message: 'Incorrect password'
});
}
} else{ // 아이디 다름
return done(null, false, {
message: 'Incorrect username'
});
}
}
));
app.post('/auth/login_process',
passport.authenticate('local', {
successRedirect: '/',
failureRedirect: '/auth/login'
}));
// 라우팅 사용
app.use('/', homeRouter);
app.use('/topic', topicRouter);
app.use('/author', authorRouter);
app.use('/auth', authRouter);
// 오류 처리
app.use(function(req, res, next){
res.status(404).send('404 - Sorry cannot find that!');
});
app.use(function(err, req, res, next){
console.error(err.stack);
res.status(500).send('500 - Something broke!');
});
app.listen(3000);
< home.js >
const express = require('express');
const router = express.Router();
// 파일 분리
const db = require('../lib/db.js');
const template = require('../lib/template.js');
const auth = require('../lib/auth.js');
// 메인 화면
router.get('/', function(request, response){
console.log('/', request.user);
db.query(`SELECT * FROM topic`, function(error, topics){
if(error) throw error;
var title = 'Welcome';
var description = 'Hello, Node.js';
var list = template.list(topics);
var html = template.HTML(title, list,
`
<h2>${title}</h2>${description}
<img src="/images/squirrel.jpg" style="width: 300px; display: block; margin-top" 10px">
`,
`<a href="/topic/create">create</a>`,
auth.statusUI(request, response)
);
response.send(html);
});
});
module.exports = router;
< lib/auth.js >
module.exports = {
isLogin: function(request, response){ // 로그인 여부 확인
if(request.user){
return true;
} else{
return false;
}
},
statusUI: function(request, response){
var authStatusUI = '<a href="/auth/login">login</a>';
if(this.isLogin(request, response)){
authStatusUI = `${request.user.nickname} | <a href="/auth/logout">logout</a>`;
}
return authStatusUI;
}
}
< routes/auth.js >
const express = require('express');
const router = express.Router();
const sanitizeHtml = require('sanitize-html');
// 파일 분리
const db = require('../lib/db.js');
const template = require('../lib/template.js');
router.get('/login', function(request, response){
db.query(`SELECT * FROM topic`, function(error, topics){
db.query(`SELECT * FROM author`, function(error2, authors){
var title = 'Login';
var list = template.list(topics);
var html = template.HTML(sanitizeHtml(title), list,
`
<form action="/auth/login_process" method="post">
<p><input type="text" name="id" placeholder="id"></p>
<p><input type="password" name="password" placeholder="password"></p>
<p>
<input type="submit" value="login">
</p>
</form>
`,
``
);
response.send(html);
});
});
});
router.get('/logout', function(request, response){
request.logout();
request.session.save(function(err){
response.redirect('/');
});
});
module.exports = router;
flash message를 이용해서 로그인에 실패했을 때 실패 문구가 뜰 수 있도록 하자.
우선 connct-flash를 설치해야 한다. ( passport가 flash를 이용하는거지, passport에 flash가 내장되어 있지 않음 )
npm install -s connect-flash
connect-flash는 세션을 사용하기 때문에 꼭 세션 다음에 미들웨어를 실행해주자.
app.use(flash());
app.get('/flash', function(request, response){
request.flash('info', 'Flash is back!');
response.redirect('/');
});
app.get('/flash-display', function(request, response){
response.render('index', {messages: request.flash('info')});
});
- info에 'Flash is back!'이라는 문구를 기록하여 플래시 메시지가 추가됨
세션에 flash message가 추가된 것을 볼 수 있다. 즉, flash는 세션 스토어에 입력값을 저장한다.
코드를 아래와 같이 수정하고 session을 다시 확인해보자.
app.get('/flash', function(request, response){
request.flash('msg', 'Flash is back!!');
response.send('flash');
});
flash() 안에서 값을 수정하면 flash message가 추가되었다.
또 코드를 수정해보자.
app.get('/flash-display', function(request, response){
var fmsg = request.flash();
console.log(fmsg);
response.send(fmsg);
});
첫 번째 flash가 없어진 것을 볼 수 있다. 다시 VSC를 들어가면, flash 객체가 비어있는 것을 볼 수 있고, 실제로 reload를 하면 flash가 사라진다.
이를 통해 flash는 일회용성 메세지라는 점을 알 수 있다.
여기까지가 flash에 대한 설명이고 이제 passport에 flash를 적용해보자.
app.post('/auth/login_process',
passport.authenticate('local', {
successRedirect: '/',
failureRedirect: '/auth/login',
failureFlash: true
}));
틀리게 로그인했을 때 세션에 에러 메세지가 추가된 것을 볼 수 있다.
아까 했던 app.use('/flash')랑 app.use('/flash-display')는 지우고, routes/auth.js로 가서 로그인 파트를 아래와 같이 바꾼다.
router.get('/login', function(request, response){
var fmsg = request.flash();
var feedback = '';
if(fmsg.error){
feedback = fmsg.error[0];
}
db.query(`SELECT * FROM topic`, function(error, topics){
db.query(`SELECT * FROM author`, function(error2, authors){
var title = 'Login';
var list = template.list(topics);
var html = template.HTML(sanitizeHtml(title), list,
`
<div style="color:red;">${feedback}</div>
<form action="/auth/login_process" method="post">
<p><input type="text" name="id" placeholder="id"></p>
<p><input type="password" name="password" placeholder="password"></p>
<p>
<input type="submit" value="login">
</p>
</form>
`,
``
);
response.send(html);
});
});
});
그럼 이렇게 에러 메시지를 flash message로 띄울 수 있다.
그리고 passport.js를 만들어서 passport 관련 코드들을 그 파일에 정리하는 리팩토링도 진행하였다.
근데 로그인을 했을 때처럼 새로고침을 해야지 flash message가 작동한다. request에 적용되는 것이 다 조금씩 늦는거 같은데 이를 어떻게 해결해야할까 ?!
+ 리팩토링 이후로 request.user이 undefined로 떠서 로그인이 안됨 !!!
>>> main.js에서 코드들의 위치를 바꿔주어서 해결했다.
const express = require('express');
const app = express();
// 미들웨어 불러오기
const compression = require('compression');
const helmet = require('helmet');
const session = require('express-session');
const fileStore = require('session-file-store')(session);
const flash = require('connect-flash');
// 미들웨이 사용
app.use(express.urlencoded({extended:false}));
app.use(compression());
app.use(express.static('public'));
app.use(helmet());
app.use(session({
httpOnly: true,
secret: 'qwertyasdfg',
resave: false,
saveUninitialized: true,
store: new fileStore()
}));
app.use(flash());
const passport = require('./lib/passport.js')(app);
// 라우팅 불러오기
const homeRouter = require('./routes/home.js');
const topicRouter = require('./routes/topic.js');
const authorRouter = require('./routes/author.js');
const authRouter = require('./routes/auth.js')(passport);
// 라우팅 사용
app.use('/', homeRouter);
app.use('/topic', topicRouter);
app.use('/author', authorRouter);
app.use('/auth', authRouter);
// 오류 처리
app.use(function(req, res, next){
res.status(404).send('404 - Sorry cannot find that!');
});
app.use(function(err, req, res, next){
console.error(err.stack);
res.status(500).send('500 - Something broke!');
});
app.listen(3000);
- passport 변수 선언을 아래로 내리고
- 라우팅 관련 코드도 아래쪽으로 내렸다.
'Javascript > Node.js' 카테고리의 다른 글
검색 기능 (0) | 2021.07.21 |
---|---|
Multi User (0) | 2021.07.19 |
Express Session& Auth - session 미들웨어, 인증 구현 (0) | 2021.07.10 |
쿠키와 인증 - 인증 구현 (0) | 2021.07.10 |
쿠키와 인증 - Session vs Permanent, Secure & HttpOnly, path & domain (1) | 2021.07.09 |