Passport란?
현재 로그인한 유저, 클라이언트에는 쿠키를 서버에는 세션을 설정할 수 있는 미들웨어
NodeJS에 적용
- passport를 애플리케이션의 미들웨어로 적용하기 전 로그인 전략(Strategy)을 짤 필요가 있음
-> 네이버로그인으로 접근하는지, 자체 로그인 방식으로 접근하는지에 대한 전략 - 전략 코드를 만들기 위해 passport라는 폴더를 만들어 전략 실행을 위한 index.js 파일과 local.js(로그인 전략) 파일을 만듦
passport/local.js
const passport = require('passport');
const { Strategy: LocalStrategy } = require('passport-local');
const bcrypt = require('bcrypt');
const db = require('../models');
module.exports = () => {
passport.use(new LocalStrategy({
usernameField: 'userId',
passwordField: 'password',
}, async (userId, password, done) => {
try{
const user =await db.User.findOne({ where: { userId }});
if(!user) {
return done(null, false, { reason: '존재하지 않는 사용자입니다!'});
}
const result = await bcrypt.compare(password, user.password);
if(result) {
return done(null, user);
}
return done(null, false, {reason: '비밀번호가 틀렸습니다!'});
}catch(e){
console.error(e);
return done(e);
}
}));
}
- npm으로 받은 passport 미들웨어를 가지고 Strategy 객체를 생성
- 유저 Id 필드는 userId, 비밀번호 필드는 password
- done의 인자 -> (오류, 성공, 로직상 에러 발생 시 메시지)
passport/index.js
const passport = require('passport');
const local = require('./local');
const db = require('../models');
module.exports = () => {
passport.serializeUser((user, done) => {
return done(null, user.id);
});
passport.deserializeUser(async (id, done) => {
try {
const user = await db.User.findOne({
where: { id },
include: [{
model: db.Post,
as: 'Posts',
attributes: ['id'],
}, {
model: db.User,
as: 'Followings',
attributes: ['id'],
}, {
model: db.User,
as: 'Followers',
attributes: ['id'],
}],
});
return done(null, user); // req.user에 저장됨
} catch (e) {
console.error(e);
return done(e);
}
});
local();
}
- passport.serializeUser
- login router에서 req.login을 호출했을 때 실행됨(로그인 요청 시 실행) - local 로그인 전략에서 에러가 없는 경우 서버 쪽에 [{ id: 3, cookie: 'asdfe' }] 형태로 쿠키 정보 관리
-> 프런트에서 서버로 쿠키를 보내면 그 쿠키가 무슨 id에 연결되어 있는지 알 수 있음 - passport.deserializeUser
- 클라이언트에서 요청을 보내올 때마다 실행
(실무에서는 서버에 가해지는 무리를 줄이기 위해 deserializeUser 결과물을 캐싱) - 서버는 메모리에서 id 정보밖에 찾을 수 없으므로 디비에서 나머지 정보들을 불러옴
-> deserialsize가 req.user를 만들어줌 - local()로 전략 코드를 연결
index.js
const express = require('express');
const morgan = require('morgan');
const db = require('./models');
const cors = require('cors');
const cookieParser = require('cookie-parser');
const expressSession = require('express-session');
const dotenv = require('dotenv');
//현재 로그인한 유저 찾기, 클라이언트엔 쿠키 서버엔 세션 설정
const passport = require('passport');
const passportConfig = require('./passport');
...
dotenv.config();
const app = express();
passportConfig();
...
app.use(cors({
//이 두 속성의 값을 true를 줘야 쿠키를 줄 수 있다. 도메인이 같으면 필요 없음.
origin: true,
credentials: true, //이 부분은 클라이언트랑 서버랑 둘 다 설정해주어야한다.
}));
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(expressSession({
resave: false,
saveUninitialized: false,
secret: process.env.COOKIE_SECRET,
//자바스크립트로 쿠키 접근 금지 설정
cookie:{
httpOnly: true,
secure: false, //https를 쓸 때 true;
},
name: 'bigrings',
}));
app.use(passport.initialize());
app.use(passport.session());
...
- 프로젝트의 루트 index에서 passport를 실행해주어야 미들웨어로서 작동
- passportConfig라는 이름으로 passport/index.js의 export를 받아와 실행
- 만약 도메인이 다르다면 cors를 사용하여 origin과 credentials 속성 값을 true로 주어야 쿠키를 줄 수 있음
- credential은 클라이언트 쪽에서도 쿠키를 보내려면 credential값을 true로 주어야 함
- cookieParser의 인자 값과 expressionSession의 secret 속성 값은 쿠키의 값을 암호화하여 쿠키의 용도를 가리기 위한 값
- 소스 자체가 털릴 가능성이 존재하기 때문에. env를 사용하여 값을 넣어줌
- expressionSession의 cookie 객체의 httpOnly 속성 true -> 자바스크립트로 쿠키 접근 금지
secure 속성 true -> https를 사용하는 경우 - passport.initialize(), passport.session()을 미들웨어로 등록하면 passport 사용 가능
(passport.session()은 expressionSession을 사용하기 때문에 expressionSession 밑에서 실행되어야 한다.)
routes/user.js
router.post('/login', (req, res, next) => {
//콜백 함수 인자로써 local 파일의 done에서 넣어준 값들이 들어온다.
//카카오 네이버 로그인이면 그 전략 파일을 불러와라
passport.authenticate('local', (err, user, info) => {
if(err){
console.error(err);
return next(err);
}
if(info){
return res.status(401).send(info.reason);
}
return req.login(user, async (loginErr) => {
try {
if (loginErr) {
return next(loginErr);
}
//얕은 복사
const fullUser = await db.User.findOne({
where: {id: user.id},
include: [{
model: db.Post,
as: 'Posts', //associate에서 as를 넣어줬으면 똑같이 넣어주어야한다.
attributes: ['id'],
}, {
model: db.User,
as: 'Followings',
attributes: ['id'],
}, {
model: db.User,
as: 'Followers',
attributes: ['id'],
}],
attributes: ['id', 'nickname', 'userId'],
});
console.log(fullUser);
return res.json(fullUser);
}catch (e) {
next(e);
}
});
})(req, res, next);
});
- 로그인 요청을 처리하는 라우터
- 로그인 전략 코드에서 사용한 done의 인자로 넣어준 값들이 여기서(passport.authenticate) 사용
- req.login을 사용하여 서버 쪽 세션에 userId와 쿠키 값을 저장
- 필요한 정보들을 디비에서 가져와 클라이언트로 보내줌
Reference
'Javascript > Node.js' 카테고리의 다른 글
ORM(Sequelize) & 데이터베이스 (0) | 2020.06.15 |
---|---|
SuperTest 연습 (0) | 2020.06.11 |
테스트 주도 개발(TDD) (0) | 2020.06.10 |
익스프레스JS (0) | 2020.06.10 |
NodeJS의 특징 (0) | 2020.06.09 |
댓글