로그인 기능만 제대로 구현할 수 있어도,
백엔드의 9할은 끝난 것이다.
- 취준생 삼다수🤔 -
0. 세팅
- npm init
- npm i sequelize mysql2 sequelize-cli
- mysql2는 노드와 mysql DB 연결을 위한 드라이버 (mysql X)
- npx sequelize init
- config, migrations, models, seeders 파일 자동 생성
- 파일 생성 - public, .env, views, routes, controllers, passport 파일 수동 생성
- npm i express cookie-parser express-session morgan multer dotenv nonjucks
- npm i -D nodemon
- app.js 제작
- scripts에 "start" : "nodemon app.js", 추가
계층적 호출이 대세이다.
app에서 라우터를 호출하고, 라우터는 컨트롤러를 호출하고, 컨트롤러는 서비스를 호출한다.
시퀄라이즈 ORM을 사용하여 JS로 SQL을 다룰 수 있다.
(ORM에는 한계가 있기 때문에 결국 SQL을 배워야 한다.)
1. passport
인증을 위한 node의 모듈이다.
세션쿠키 처리 등 복잡한 작업을 대신해준다.
passport는 다양한 인증방법을 제공하는데 이를
strategies(전략)이라고 한다.
stratege는 두가지로 구분된다.
a. local strategy : 로컬 DB를 이용한 로그인 인증하는 방식
b. social authentication : 소셜네트워크를 이용한 로그인 인증하는 방식
2. 설치
npm i passport passport-local passport-kakao bcrypt
bcrypt는 비밀번호를 해시를 이용해 암호화 해주는 모듈이다. (일방향, 복호화 불가)
3. 설정
a. 필요 미들웨어 설정
// app.js
const passport = require("passport");
const passportConfig = require("./passport");
passportConfig(); // 패스포트 설정
// - 세션 -
// 아래 미들웨어는 세션설정 하단에 작성
app.use(passport.initialize()); // req.user, req.login, req.isAuthenticate, req.logout 생성
app.use(passport.session()); // connect.sid라는 이름으로 세션 쿠키가 브라우저로 전송
b. 필요 passport 설정
// passport/index.js
const passport = require("passport");
const local = require("./localStrategy");
const User = require("../models/user");
module.exports = () => {
// 로그인이 성공했을때 유저정보를 세션에 저장
// connect.sid=`user.id` 전달된다.
passport.serializeUser((user, done) => {
console.log("serialize");
done(null, user.id);
});
// 세션에 있는 사용자의 식별자를 받아서 DB에서 조회
passport.deserializeUser((id, done) => {
console.log("deserialize");
User.findOne({
where: { id },
include: [
{
model: User,
attributes: ["id", "nick"],
as: "Followers",
},
{
model: User,
attributes: ["id", "nick"],
as: "Followings",
},
],
})
.then((user) => {
console.log("user", user);
// 성공했다
done(null, user);
})
// 오류났다
.catch((err) => done(err));
});
local();
};
// 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에 로컬스토리지를 등록하는 작성
// 이메일 로그인 했을때, 어떻게 할지
// 로그인을 시켜줘도 되는지 안되는지를 판단하는 코드
passport.use(
new LocalStrategy(
{
// usernameField passwordField passReqToCallback 이거 약속된 키이다.
// req.body.email
usernameField: "email",
// req.body.password
passwordField: "password",
passReqToCallback: false,
},
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);
}
}
)
);
};
c. 필요 middleware 설정
// middlewares/index.js
// 미들웨어를 통해서 로그인이 되어 있는지 확인
// 로그인 확인은 req.isAuthenticated()를 통해서 확인
// 로그인이 되어있어야 하는 경우를 위한 미들웨어
// /profile ...
exports.isLoggedIn = (req, res, next) => {
if (req.isAuthenticated()) {
next();
} else {
res.status(403).send("로그인 필요");
}
};
// 로그인이 "안"되어있어야 하는 경우를 위한 미들웨어
// /auth/login ...
exports.isNotLoggedIn = (req, res, next) => {
if (!req.isAuthenticated()) {
next();
} else {
const message = encodeURIComponent("로그인한 상태입니다.");
res.redirect(`/?error=${message}`);
}
};
4-1. 가입(join) 흐름
가입에는 passport가 사용되지 않는다.
passport는 오로지 인증에 관한 모듈이다.
// controllers/auth.js
const bcrypt = require("bcrypt");
const passport = require("passport");
const User = require("../models/user");
// 가입 (이건 passport를 상관이 전혀 없다.)
// 이 가입 코드는, 회원가입만 도와줌, 바로 로그인 시켜주지는 않음
exports.join = async (req, res, next) => {
// req의 body에서 데이터 가져오기
const { email, nick, password } = req.body;
try {
// validation
const exUser = await User.findOne({ where: { email } });
if (exUser) {
return res.redirect("/join?error=exist");
}
// 암호 해쉬에 돌리기
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);
}
};
4-2. 로그인(login) "시도" 요청 흐름
- /auth/login 으로 요청 입장
- 라우터에서 passport.authenticate 호출
- authenticate의 첫번째 인수에 따라서 로그인 전략 수행("local"이면, LocalStrategy)
- 로그인 성공시 사용자 정보 객체와 함께 req.login호출
- req.session에 사용자 아이디만 저장해서 세션 생성
- 무거워짐을 방식하고자
- express-session 설정을 기반으로 브라우저에 connect.sid세션쿠키 전송
- app.use(passport.session()); 에 의해
- 브라우저는 connect.sid="~" 형태의 쿠키를 받는다.
- 로그인 완료
// controllers/auth.js
const bcrypt = require("bcrypt");
const passport = require("passport");
const User = require("../models/user");
// POST /auth/login
// 로그인 (받아온 데이터와 가지고 있는 데이터를 비교)
// 이게 passport랑 상관이 있어지는 거다. 인증!의 과정이니까
exports.login = (req, res, next) => {
// 인증을 위한 미들웨어
// 중요중요중요!!!! local만나면 등록해두었던 LocalStrategy가 실행된다.
passport.authenticate("local", (authError, user, info) => {
if (authError) {
// 서버실패
console.error(authError);
return next(authError);
}
if (!user) {
// 로직실패
return res.redirect(`/?error=${info.message}`);
}
return req.login(user, (loginError) => {
// 로그인 성공
if (loginError) {
console.error(loginError);
return next(loginError);
}
return res.redirect("/");
});
})(req, res, next); // 미들웨어 내의 미들웨어에는 (req, res, next)를 붙입니다.
};
4-3. 로그인 "성공 후" 요청 흐름
- 요청 입장
- passport.session 미들웨어가 passport.deserializeUser 호출
- connect.sid 세션 쿠키 읽고, 세션 객체 찾은 뒤, req.session 제작
- req.session에 저장된 아이디를 기반으로 DB 조회
- DB에서 조회한 정보를 req.user에 저장
- 라우터에서 req.user 객체 사용가능 상태
'Node.js' 카테고리의 다른 글
5. 테스트 (jest, supertest) [express] (1) | 2024.12.09 |
---|---|
4. 웹 API 서버 [express] (0) | 2024.12.09 |
2. express (서버제작 level 2) [express] (0) | 2024.11.11 |
1. http (서버제작 level 1) [express] (1) | 2024.11.05 |
3. 노드 기본 [Node] (8) | 2024.11.05 |