회원인증방법
- Session-Based
- 페이지에서 서버로 로그인 요청시
- 서버는 쿠키를 전달하게 됨
- 쿠키란 브라우저에 저장할 수 있는 긴 문자열
- 그래서 쿠키에 Session_ID 가 담겨져 있음
- 그래서 서버 메모리에 유저의 로그인 정보를 저장하게 되면서
- 세션이 저장되었다는 정보를 쿠키로 만들어서 브라우저에 전송
- 이후 브라우저는 저장하게 되고 그것이 로그인을 한 상태가 되는 것
- 로그인 후 활동으로 예를 들어 마이페이지를 보여달라고 할 때 자동으로 쿠키를 전송하게 되고, 서버의 Session Store에서 쿠키 안에 있는 Session_ID를 찾아서 로그인 했는지 확인 후 페이지를 보여주게 됨
- Session-Based의 특징은 로그인 상태를 저장하는 것
- JWT (Token-Based, JSON Web Token)
- 로그인시 서버에선 JSON Web Token을 브라우저에 발행후 전송하게 됨.
- JSON Web Token 도 마찬가지로 긴 문자열
- 이후 브라우저는 해당 쿠키를 localstorage 또는 쿠키에 저장하게 됨.
- 이후 마이페이지 같은 곳에 방문하려고 할 때 마다 JSON Web Token을 Header에 담아 서버에 전송하게 됨.
- 서버는 토큰이 유효한지 검사를 하게 됨
- JWT의 특징은 유저의 로그인 상태를 저장할 필요 없다는 것
- 그렇기에 REST 원칙 중 하나인 stateless 해야한다는 것을 지킬 수 있게 됨.
- 로그인시 서버에선 JSON Web Token을 브라우저에 발행후 전송하게 됨.
- OAuth (Open Authentication)
- 다른 사이트의 프로필 정보를 가져오는 것
- 구글 또는 페이스북에서 해당 유저의 프로필 정보를 가져오는 것
- Social 로그인 버튼을 클릭 시 구글 계정 정보를 브라우저에 제공하는걸 동의하는지 확인
- 이후 이름, 성별 등등을 가지고 계정, 세션, JWT를 만들어주기
- OAuth 특징 Password 입력이 필요 없다는 간편함, 유저는 버튼 클릭으로 로그인하는 간편함
- 단점: 없어진 사이트로 OAuth를 사용할 경우 로그인하지 못하게 됨.
- 다른 사이트의 프로필 정보를 가져오는 것
해당 강의에서는 간편한 Session-Based 방식을 사용
npm i passport passport-local express-session
어떻게 설명을 적는게 좋을까하다가 그냥 주석으로 작성했음.
아래 코드는 로그인 관련 코드만 있는 코드고,
그 밑에 코드는 server.js 모든 코드를 작성해 놓은 것.
그리고 페이지의 경우엔 따로 적을 만큼의 내용이 없어서 생략.
로그인 코드
// 설치한 라이브러리를 가져오기
const passport = require("passport");
const localStrategy = require("passport-local").Strategy;
const session = require("express-session");
// middleware
// app.use() 를 사용하게 되면 () 안의 미들웨어를 사용하겠다라는 의미.
// middleware란?
// 우선 웹서버는 요청-응답을 해주는 것.
// 그래서 요청-응답 중간에 실행되는 코드를 미들웨어라고 한다.
app.use(
session({ secret: "비밀코드", resave: true, saveUninitialized: false })
); // <-- secret은 세션을 만들때의 비밀번호 같은 것.
app.use(passport.initialize());
app.use(passport.session());
app.get("/login", (req, res) => {
res.render("login.ejs");
});
// 기존 post 요청과는 달리 맞는지 인증 검사를 해야하기에 passport.authenticate('local') 를 추가해주기
// 해당 코드는 local 방식으로 회원인지 인증해달라는 코드
// 그리고 {} 를 추가하면 셋팅을 더 추가로 할 수 있게 됨.--> 그래서 현재는 로그인 실패시 /fail 페이지로 이동시켜달라는 코드
app.post(
"/login",
passport.authenticate("local", {
failureRedirect: "/fail",
}),
(req, res) => {
// 아이디, 비번 맞으면 메인 페이지로 보내주기.
res.redirect("/");
}
);
// 로그인 유저만 mypage에 입장 가능하도록 해야하니깐 middleware 추가하기
// 아래 isLogin 함수는 req.user 가 있는지 확인하는 middleware
// next() 는 다음으로 진행을 의미 (통과)
function isLogin(req, res, next) {
if (req.user) {
// <--- req.user는 로그인 후 세션이 있으면 항상 있는 데이터
next();
} else {
res.redirect("/login");
}
}
app.get("/mypage", isLogin, (req, res) => {
// console.log(req.user); // <--- deserializeUser() 의 결과를 받아온 db에 있는 유저의 데이터
res.render("mypage.ejs", { userData: req.user });
});
// 아이디와 비번을 인증하는 세부 코드의 경우엔 상세히 작성해야 함.
// 그래서 인증하는 방법을 strategy로 작성
passport.use(
new localStrategy(
{
usernameField: "id", // form 태그의 name 속성의 id 값
passwordField: "pw", // form 태그의 name 속성의 pw 값
session: true, // 로그인 후 세션을 저장할 것인지에 대한 셋팅
passReqToCallback: false, // 아이디/비밀번호 말고도 다른 정보로 검증을 할 것인지에 대한 셋팅
// 만약 passReqToCallback: true로 한다면 밑의 (id, pw, done) 에서 req를 추가해서 req.body 하면 데이터가 나온다고 한다.
},
(id, pw, done) => {
// done(1번째 인자: 서버에러와 같은 db 연결 불가 등, 2번째 인자: 요청을 성공했을 때 사용자 db 데이터 만약 실패의 경우 false 넣어야 함, 3번째 인자: 에러 메시지)
db.collection("login").findOne({ id: id }, (err, data) => {
if (err) return done(err);
if (!data)
// 결과가 없다면 --> db에 대항 아이디가 없다면
return done(null, false, { msg: "존재하지 않는 아이디 입니다." });
if (pw == data.pw) {
// pw가 암호화 되어있지 않기에 보안이 좋지 않지만 지금은 우선 이렇게 진행함.
// db에 아이디가 있다면, 입력한 비밀번호와 db에 있는 비번이랑 확인해보기.
// 아이디와 비번이 맞아서 로그인을 성공하면 세션 정보를 만들어줘야 함 (로그인 했는지 확인하기 위해)
return done(null, data); // <--- 여기 성공시의 data가 257번줄의 serializeUser((user, done))의 user 데이터에 들어가게 됨
} else {
return done(null, false, { msg: "잘못된 비밀번호 입니다." });
}
});
}
)
);
// id를 이용해서 세션을 저장시키는 코드 (로그인 성공시 발동)
passport.serializeUser((user, done) => {
done(null, user.id); // 보통 id를 이용해서 세션을 저장시키기에 user.id로 세션을 만들어 주면서 쿠키도 만들어주게 됨. 그리고 쿠키안에는 로그인 했다는 정보가 들어감
});
// 나중에 마이페이지 접속시 발동할 예정 (이 세션 데이터를 가진 사람을 db에서 찾아줘) --> 즉 해당 세션 데이터를 가진 유저의 정보를 가져오기
// 여기서 done(null, data)로 받아온 data 값은 mypage 를 get 할때 가져오는 res.user의 데이터에 담겨져 있음
passport.deserializeUser((id, done) => {
// 위의 파라미터 id값은 258번줄의 user.id의 id를 가지고 있음
db.collection("login").findOne({ id: id }, (err, data) => {
done(null, data);
});
});
server.js 모든 코드
const express = require("express"); //위에서 작성한 라이브러리를 설치
const app = express(); // 설치한 라이브러리로 객체를 생성해줘
const bodyParser = require("body-parser"); // body-parser 가져오기
// PUT, DELETE 요청 가능하게 하는 방법
const methodOverride = require("method-override");
app.use(methodOverride("_method"));
app.use(bodyParser.urlencoded({ extended: true }));
app.set("view engine", "ejs");
app.use("/public", express.static("public"));
var db;
const MongoClient = require("mongodb").MongoClient;
MongoClient.connect(
"각자 다른 주소",
(에러, client) => {
if (에러) return console.log(에러);
db = client.db("db 이름"); // database에 연결하는 코드
// db에 내 이름과 나이를 저장하기
// db.collection('post').insertOne('저장할 데이터', (에러, 결과)=> {
// console.log('저장완료');
// })
// 아래 코드는 post라는 파일에 inserOne으로 저장하는데 데이터는 아래 작성한 객체형식의 데이터를 넣을거야 라는 의미
// 저장할 데이터는 객체형식으로 작성하기
// db.collection("post").insertOne(
// { 이름: "admin", 나이: 10, _id: 0 },
// (에러, 결과) => {
// console.log("저장완료");
// }
// );
// 콘솔에 input 태그에 작성한 값이 출력되는 것을 확인할 수 있다.
app.listen(8080, () => {
console.log("listening on 8080");
});
}
);
// listen(서버 포트 번호, 해당 서버에 실행할 코드)
// get 요청 API 만들기
app.get("/pet", (req, res) => {
res.send("펫용품 쇼핑할 수 있는 페이지 입니다.");
});
app.get("/beauty", (req, res) => {
res.send("뷰티용품 쇼핑할 수 있는 페이지 입니다.");
});
app.get("/", (req, res) => {
// res.sendFIle(__dirname + '보낼파일경로')
res.render("index.ejs");
});
app.get("/write", (req, res) => {
// res.sendFIle(__dirname + '보낼파일경로')
res.render("write.ejs");
});
app.get("/edit/:id", (req, res) => {
db.collection("post").findOne(
{ _id: parseInt(req.params.id) },
(error, data) => {
if (data === null) {
res.render("error.ejs");
} else {
res.render("edit.ejs", { data: data });
}
}
);
});
app.put("/edit", (req, res) => {
// form에 담긴 데이터를 서버에서 해당 게시글에 맞는 id를 찾아서 데이터를 수정하기.
// update를 할 때는 updateOne({어떤 게시글}, {수정값}, (err, data) => {})
// 여기서 $set 은 operator로 ~가 있으면 수정하고, 없다면 추가해주세요 라는 의미
console.log(req.body);
db.collection("post").updateOne(
{ _id: parseInt(req.body.id) },
{ $set: { 제목: req.body.title, 날짜: req.body.date } },
(err, data) => {
res.redirect("/list");
}
);
});
app.post("/add", (req, res) => {
res.redirect("/list");
console.log(req.body.title);
console.log(req.body.date);
// id 값을 매기기 위해 총 데이터 개수를 가져오는 방법
// 단 예를 들어 하나의 데이터를 삭제했을 때 같은 id를 가진 새로운 데이터가 생기면 안되기에
// 기존의 id 값이 아닌 +1 한 id 값을 가져와야함.
// 위와 같은 문제를 해결하기 위해 1개의 collection을 더 만들어서 관리할 예정
// counter라는 collection에서 name: '게시물 개수'인 데이터를 찾아달라는 코드
db.collection("counter").findOne({ name: "게시물 개수" }, (error, data) => {
const id = data.totalPost;
db.collection("post").insertOne(
{ 제목: req.body.title, 날짜: req.body.date, _id: id + 1 },
(에러, 결과) => {
// 만약 여러개를 수정하겠다면 updateMany()
// db.collection('counter').updateOne({어떤데이터를 수정할지}, {operator : {변경할key : 수정값}}, ()=>{})
// operator ==> $set = 변경, $inc = 기존값에 더해줄 값, $min = 기존값보다 적을 때만 변경, $ rename = key값 이름 변경 ...
// 만약 딱히 처리할게 없다면 콜백함수를 지워도 됨
db.collection("counter").updateOne(
{ name: "게시물 개수" },
{ $inc: { totalPost: 1 } },
() => {
console.log("저장완료");
}
);
}
);
});
});
// 과제
// /list 로 GET 요청하면 실제 DB에 저장된 데이터들로 꾸며진 HTML 보여주기
app.get("/list", (req, res) => {
// DB에 있는 데이터를 꺼내려고 할 때는 어떤 데이터를 꺼낼지 작성해줘야 한다.
// 그래서 DB에 저장된 post라는 collection안의 어떠한 변수로 적힌 데이터를 꺼내달라고 코드를 작성해야함.
// 그리고 순서가 있는데 데이터를 꺼내고 ejs 파일을 불러오는게 순서가 맞으니 주의 해야함!
// db.collection('post').find() // toArray()를 안붙이면 메타데이터까지 다 가져오게 되기에 아래처럼 작성해주기.
db.collection("post")
.find()
.toArray((error, data) => {
console.log(data);
res.render("list.ejs", { posts: data });
}); // 해당 db에 있는 모든 데이터를 가져오게 됨.
});
app.delete("/delete", (req, res) => {
// req.body를 하게되면 ajax로 요청시 보낸 data를 받아서 확인할 수 있다.
// 그리고 아래처럼 req.body로 보낸 숫자 1의 데이터가 받아올때는 문자 '1'로 받아와지기에 Int로 변경 시켜주기.
const id = parseInt(req.body._id);
// req.body에 담겨온 게시물번호를 가진 글을 db에서 찾아서 삭제해주세요.
// deleteOne({어떤 항목을 삭제할지}, ()=>{}) --> 삭제 메서드
db.collection("post").deleteOne({ _id: id }, (error, data) => {
console.log("삭제완료");
// 응답코드 200을 보내주기.
// .send({}) 로 메시지를 함께 보내주기.
if (error) {
res.status(400).send({ message: "실패" });
} else {
res.status(200).send({ message: "성공" });
}
});
});
// /detail로 접속하면 detail.ejs를 보여주기 (URL Parameter) ==> 각 상세페이지의 서로 다른 URL
app.get("/detail/:id", (req, res) => {
// :?? 를 작성하게 되면 parameter를 받아서 가변 주소를 가져오게 된다.
// res.render('detail.ejs', {이런 이름으로: 이런 데이터를 전송 가능})
// db에서 우리가 찾을 데이터의 _id에 해당하는 데이터를 가져와달라는 건데
// parameter의 값을 가져오려고 할 경우 req.params.?? 를 작성하게 되면 가져올 수 있다.
// 그리고 위에서 확인했던것처럼 req 데이터로 날라오는 값이 모두는 잘 모르겠지만 대부분이 string type으로 날라오기에 정수화 시켜주기!!!
db.collection("post").findOne(
{ _id: parseInt(req.params.id) },
(error, data) => {
if (data === null) {
console.log("데이터 없다.");
res.render("error.ejs");
} else {
res.render("detail.ejs", { data: data });
}
}
);
});
// 설치한 라이브러리를 가져오기
const passport = require("passport");
const localStrategy = require("passport-local").Strategy;
const session = require("express-session");
// middleware
// app.use() 를 사용하게 되면 () 안의 미들웨어를 사용하겠다라는 의미.
// middleware란?
// 우선 웹서버는 요청-응답을 해주는 것.
// 그래서 요청-응답 중간에 실행되는 코드를 미들웨어라고 한다.
app.use(
session({ secret: "비밀코드", resave: true, saveUninitialized: false })
); // <-- secret은 세션을 만들때의 비밀번호 같은 것.
app.use(passport.initialize());
app.use(passport.session());
app.get("/login", (req, res) => {
res.render("login.ejs");
});
// 기존 post 요청과는 달리 맞는지 인증 검사를 해야하기에 passport.authenticate('local') 를 추가해주기
// 해당 코드는 local 방식으로 회원인지 인증해달라는 코드
// 그리고 {} 를 추가하면 셋팅을 더 추가로 할 수 있게 됨.--> 그래서 현재는 로그인 실패시 /fail 페이지로 이동시켜달라는 코드
app.post(
"/login",
passport.authenticate("local", {
failureRedirect: "/fail",
}),
(req, res) => {
// 아이디, 비번 맞으면 메인 페이지로 보내주기.
res.redirect("/");
}
);
// 로그인 유저만 mypage에 입장 가능하도록 해야하니깐 middleware 추가하기
// 아래 isLogin 함수는 req.user 가 있는지 확인하는 middleware
// next() 는 다음으로 진행을 의미 (통과)
function isLogin(req, res, next) {
if (req.user) {
// <--- req.user는 로그인 후 세션이 있으면 항상 있는 데이터
next();
} else {
res.redirect("/login");
}
}
app.get("/mypage", isLogin, (req, res) => {
// console.log(req.user); // <--- deserializeUser() 의 결과를 받아온 db에 있는 유저의 데이터
res.render("mypage.ejs", { userData: req.user });
});
// 아이디와 비번을 인증하는 세부 코드의 경우엔 상세히 작성해야 함.
// 그래서 인증하는 방법을 strategy로 작성
passport.use(
new localStrategy(
{
usernameField: "id", // form 태그의 name 속성의 id 값
passwordField: "pw", // form 태그의 name 속성의 pw 값
session: true, // 로그인 후 세션을 저장할 것인지에 대한 셋팅
passReqToCallback: false, // 아이디/비밀번호 말고도 다른 정보로 검증을 할 것인지에 대한 셋팅
// 만약 passReqToCallback: true로 한다면 밑의 (id, pw, done) 에서 req를 추가해서 req.body 하면 데이터가 나온다고 한다.
},
(id, pw, done) => {
// done(1번째 인자: 서버에러와 같은 db 연결 불가 등, 2번째 인자: 요청을 성공했을 때 사용자 db 데이터 만약 실패의 경우 false 넣어야 함, 3번째 인자: 에러 메시지)
db.collection("login").findOne({ id: id }, (err, data) => {
if (err) return done(err);
if (!data)
// 결과가 없다면 --> db에 대항 아이디가 없다면
return done(null, false, { msg: "존재하지 않는 아이디 입니다." });
if (pw == data.pw) {
// pw가 암호화 되어있지 않기에 보안이 좋지 않지만 지금은 우선 이렇게 진행함.
// db에 아이디가 있다면, 입력한 비밀번호와 db에 있는 비번이랑 확인해보기.
// 아이디와 비번이 맞아서 로그인을 성공하면 세션 정보를 만들어줘야 함 (로그인 했는지 확인하기 위해)
return done(null, data); // <--- 여기 성공시의 data가 257번줄의 serializeUser((user, done))의 user 데이터에 들어가게 됨
} else {
return done(null, false, { msg: "잘못된 비밀번호 입니다." });
}
});
}
)
);
// id를 이용해서 세션을 저장시키는 코드 (로그인 성공시 발동)
passport.serializeUser((user, done) => {
done(null, user.id); // 보통 id를 이용해서 세션을 저장시키기에 user.id로 세션을 만들어 주면서 쿠키도 만들어주게 됨. 그리고 쿠키안에는 로그인 했다는 정보가 들어감
});
// 나중에 마이페이지 접속시 발동할 예정 (이 세션 데이터를 가진 사람을 db에서 찾아줘) --> 즉 해당 세션 데이터를 가진 유저의 정보를 가져오기
// 여기서 done(null, data)로 받아온 data 값은 mypage 를 get 할때 가져오는 res.user의 데이터에 담겨져 있음
passport.deserializeUser((id, done) => {
// 위의 파라미터 id값은 258번줄의 user.id의 id를 가지고 있음
db.collection("login").findOne({ id: id }, (err, data) => {
done(null, data);
});
});
'NodeJS+MongoDB' 카테고리의 다른 글
NodeJS+MongoDB Part3 - 암호화 (0) | 2023.06.27 |
---|---|
NodeJS+MongoDB Part3 - Search (0) | 2023.06.26 |
NodeJS+MongoDB Part2 + PUT (0) | 2023.06.23 |
NodeJS+MongoDB Part2 (0) | 2023.06.22 |
NodeJS+MongoDB Part1 (0) | 2023.06.04 |