일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 포렌식
- 웹해킹기초
- 자료구조
- mongoose
- 웹기초
- 웹해킹
- nodeJS
- 자바기초
- 써니나타스
- CTF
- 뷰
- 워게임
- MongoDB
- 그래프
- NavBar
- bootstrap
- node.js
- 워게임추천
- gitbash
- node
- materialize
- GIT
- 자바문제풀이
- wargame.kr
- 자바
- 웹개발
- 이진트리
- 이진탐색트리
- 포렌식워게임
- Express
- Today
- Total
보안 전공생의 공부
게시판 만들기 / login 기능 - passport package 본문
passport package를 사용해 login 기능을 만든다.
· passport : node.js에서 user authentication(사용자 인증, login)을 만들기 위해 사용하는 package
단독으로 사용 X , passport strategy package와 함꼐 사용해야 함
· passport strategy : 구체적인 인증 방법을 구현하는 package
-> 인증 방법별로 수십가지(Facebook strategy, Twitter strategy, Naver strategy 등)가 존재하기 때문에
package가 나눠지게 되었다.
실제 한 사이트에서 사용하는 strategy는 이 중 몇 개밖에 안된다.
즉, 사이트에 필요한 인증밥법만 설치하기 위해 package를 분리한 것이다.
[로그인 기본 원리]
server와 client 간의 정보교환 : 단발성(어떤 일이 단 한 번 만으로 그치는성질)
사용자의 browser(client)d에서 주소 입력 or link가 click 되면 server로 요청(request) 전달
-> server는 요청에 맞는 결과를 응답(response)
=> server-client간의 통신은 연결을 유지 X
client를 구별하기 위해서는 각각의 request에 고유한 식별코드가 필요함
이 때, 식별코드는 사이트에 처음 접속한 순간 생성되어 client의 브라우저에 저장되고, server에 요청할 때마다 server로 전달됨 / server에서는 식별코드가 session에 저장되어 어느 client로부터 요청이 오는지 구별할 수 있게 됨
로그인 성공하면 server의 session에 기록되고, 다음번 request부터는 로그인한 상태로 인식됨
로그인 == DB에 이미 등록된 user을 찾는 것
- serialize(직렬화) : 로그인 시, DB로부터 user을 찾아 session에 user 정보의 일부(전부)를 등록하는 것
- deserialize(역직렬화) : session에 등록된 user 정보로부터 해당 user를 oject로 만드는 것 -> server에 요청 올 때마다 거치는 과정
1. 필요한 package를 설치한다.
2. 코드 수정, 추가
//index.js
const express=require('express')//모듈 가져오기
const mongoose=require('mongoose')
const methodOverride = require('method-override')
const flash=require("connect-flash")
const session=require("express-session")
const passport=require("./config/passport")
const app = express()//어플리케이션 생성
// DB setting
mongoose.connect(process.env.MONGO_DB);
const db = mongoose.connection;
db.once('open', function(){
console.log('DB connected');
})
db.on('error', function(err){
console.log('DB ERROR : ', err);
});
//other setting
app.set('view engine','ejs')//템플릿 엔진
app.use(express.json());
app.use(express.static('public'));
app.use(express.urlencoded({extended:true}));
app.use(methodOverride('_method'));
app.use(flash());
app.use(session({secret:'MySecret', resave:true, saveUninitialized:true}));
app.use(passport.initialize())
app.use(passport.session())
//custom middlewares
app.use(function(req,res,next){
res.locals.isAuthenticated = req.isAuthenticated();
res.locals.currentUser = req.user;
next();
})
//routes
app.use('/', require('./routes/home'));
app.use('/posts', require('./routes/posts'))
app.use('/contacts', require('./routes/contacts'));
app.use('/home', require('./routes/home'))
app.use('/users', require('./routes/users'))
//port setting
const server=app.listen(3000, ()=>{
console.log('Start Server : localhost:3000')
})
: passport 말고 config/passport module을 변수 passport에 담았다.
passport와 passport-local package는 index.js에 require되지 않고, config의 passport.js에서 require된다.
: passport를 초기화시키는 함수
: passport와 session을 연결해주는 함수
- session을 이용하기 위해 앞서 user error처리를 하기위해 작성한 아래 코드가 반드시 필요함
: app.use에 함수를 넣은 것 = middleware
app.use에 있는 함수는 request가 올 때마다 route에 상관없이 무조건 해당 함수가 실행된다.
위치가 중요하다 ( ∵ 위에 있는 것부터 순차적으로 실행됨)
route에 들어가는 함수와 동일하게 3개의 parameter( req, res, next )를 가진다.
함수 안에 반드시 next()를 넣어야 다음으로 넘어갈 수 있다.
- req.isAuthenticated() - passport에서 제공하는 함수 (현재 로그인이 되어있으면 true, 아니면 false를 return)
- req.user - passport에서 추가하는 항목. 로그인 되면 session으로부터 user를 deserialize하여 생성된다.
- res.locals - 위의 두가지를 담고 있다. res.lcals에 담긴 변수는 ejs에서 바로 사용가능
res.locals.isAuthenticated : ejs에서 user의 로그인 유무를 확인하는 데 사용됨
res.localse.currentUser : 로그인된 user의 정보를 불러오는데 사용됨
[error]
이러한 error가 발생하였는데, custom middlewares 코드의 위치 문제때문이였다.
routes 뒤에 custom middlewares를 위치시켰더니 위의 error가 발생하여,
custom middlewares를 routes 앞으로 옮겼더니 해결되었다.
미들웨어의 로드 순서가 중요하다.
routes에 있는 루트 경로에 대한 라우팅(app.use('/', require('./routes/home'));) 이후에 custom middlewares가 로드되면,
루트 경로의 라우트 핸들러가 요청-응답 주기를 종료하므로 요청은 절대 custom middlewares로 도달하지 못하게 되며
위와 같은 error가 발생한다.
따라서 앞으로도 index.js에 추가할 middleware는 routes 앞에 위치시켜야 한다.
(route에 상관없이 무조건 해당 함수가 실행되어야 하므로)
참조 : https://expressjs.com/ko/guide/writing-middleware.html
//config/passport.js
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const User = require('../models/User');
// serialize & deserialize User
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findOne({_id:id}, function(err, user) {
done(err, user);
});
});
// local strategy
passport.use('local-login',
new LocalStrategy({
usernameField : 'username',
passwordField : 'password',
passReqToCallback : true
},
function(req, username, password, done) {
User.findOne({username:username})
.select({password:1})
.exec(function(err, user) {
if (err) return done(err);
if (user && user.authenticate(password)){
return done(null, user);
}
else {
req.flash('username', username);
req.flash('errors', {login:'The username or password is incorrect.'});
return done(null, false);
}
});
}
)
);
module.exports = passport;
: strategy들은 대부분 require 다음에 .Strategy가 붙는다 (.Strategy없이 사용해도 되는 것들도 있음)
: login시 DB에서 발견한 user를 어떻게 session에 저장할지 정하는 부분
user정보 전체를 session에 저장할 수 있지만,
session에 저장되는 정보가 과다하면 사이트 성능이 하락할 수 있고, 전체 user정보가 session에 저장되어 있으므로 user object를 변경하면(회원정보수정) 해당 부분을 변경해줘야 하는 문제들이 있으므로
user의 id만 session에 저장한다.
: request시에 session에서 어떻게 user object를 만들지를 정하는 부분
매번 request마다 user정보를 DB에서 새로 읽어오는데, user가 변경되면 바로 변경된 정보가 반영된다.
+) done함수의 첫번째 parameter는 항상 error를 담는다. error가 없다면 null을 담는다.
user 정보를 전부 session에 저장하여 DB접촉 감소 -> session에 저장되는 정보가 과다해 사이트 성능 하락할 수 있음
request마다 user를 DB에서 읽어와서 데이터의 일관성을 확보 -> 매 request마다 DB에서 user을 읽어와야 함
=> 상황에 맞게 선택
로그인 form의 username과 password항목의 이름을 넣는다.
로그인 시에 호출되는 함수
user model에서 정의한 user.authenticate 함수를 사용해 입력받은 password와 DB에 저장된 해당 user의 password(user.password) hash를 비교해서 값이 일치하면 해당 user를 done에 담아 return한다.
그렇지 않으면 username flash, error flash를 생성하고 false를 done에 담아 return한다.
//routes/home.js
const express=require('express')
const router=express.Router()
const passport=require('../config/passport')
router.get('/', function(req, res){
res.render('home/main');
});
router.get('/about', function(req, res){
res.render('home/about');
});
router.get('/login',function(req,res){
const username=req.flash('username')[0];
const errors=req.flash('errors')[0]||{};
res.render('home/login',{
username:username,
errors:errors
})
})
router.post('/login',
function(req,res,next){
const errors = {};
const isValid = true;
if(!req.body.username){
isValid = false;
errors.username = 'Username is required!';
}
if(!req.body.password){
isValid = false;
errors.password = 'Password is required!';
}
if(isValid){
next();
}
else {
req.flash('errors',errors);
res.redirect('/login');
}
},
passport.authenticate('local-login', {
successRedirect : '/posts',
failureRedirect : '/login'
}
));
// Logout
router.get('/logout', function(req, res) {
req.logout();
res.redirect('/');
});
module.exports=router;
: passport 말고 config/passport module을 변수 passport에 담았다.
passport와 passport-local package는 home.js에 require되지 않고, config의 passport.js에서 require된다
(index.js와 동일)
login view를 보여주는 route
login form에서 보내진 post request를 처리해 주는 route
두 개의 callback 중
첫번째 callback은 보내진 form의 validation을 위한 것이다.
에러가 있으면( isValid = false; ) flash를 만들고 login view로 redirect한다.
두번째 callback은 passport local strategy를 호출해서 authentiction(로그인)을 진행한다.
(참조 : http://www.passportjs.org/docs/login/)
logout을 해주는 route
passport에서 제공된 req.logout함수를 사용하여 로그아웃하고 "/"로 redirect한다.
(참조 : http://www.passportjs.org/docs/logout/)
-login view-
<!-- views/home/login.ejs -->
<!DOCTYPE html>
<html>
<head>
<%- include('../partials/head') %>
</head>
<body>
<%- include('../partials/nav') %>
<div class="container">
<h3 class="mb-3">Login</h3>
<form class="user-form" action="/login" method="post">
<div class="form-group row">
<label for="username" class="col-sm-3 col-form-label">Username</label>
<div class="col-sm-9">
<input type="text" id="username" name="username" value="<%= username %>" class="form-control <%= (errors.username)?'is-invalid':'' %>">
<% if(errors.username){ %>
<span class="invalid-feedback"><%= errors.username %></span>
<% } %>
</div>
</div>
<div class="form-group row">
<label for="password" class="col-sm-3 col-form-label">Password</label>
<div class="col-sm-9">
<input type="password" id="password" name="password" value="" class="form-control <%= (errors.password)?'is-invalid':'' %>">
<% if(errors.password){ %>
<span class="invalid-feedback"><%= errors.password %></span>
<% } %>
</div>
</div>
<% if(errors.login){ %>
<div class="invalid-feedback d-block"><%= errors.login %></div>
<% } %>
<div class="mt-3">
<input class="btn btn-primary" type="submit" value="Submit">
</div>
</form>
</div>
</body>
</html>
-nav.ejs-
<html>
<head>
<%- include('../partials/head') %>
</head>
<body>
<%- include('../partials/nav') %>
<div class="container contact contact-new">
<h2>New</h2>
<form class="cotact-form" action="/contacts" method="post">
<div class="form-group">
<label for="name">Name</label>
<input type="text" id="name" name="name" value="" class="form-control">
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="text" id="email" name="email" value="" class="form-control">
</div>
<div class="form-group">
<label for="phone">Phone</label>
<input type="text" id="phone" name="phone" value="" class="form-control">
</div>
<div>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</div>
</body>
</html>
※ 로그인이 일어날 때 코드 진행순서
1. Login 버튼 클릭 -> routes/home.js의 router.post('/login', ~)가 실행
1) login route 실행 (첫번째 callback)
2) passport local strategy (config/passport.js)를 호출해서 로그인 진행 (두번째 callback)
2. 로그인이 성공하면 config/passport.js의 serialize 코드 실행
3. routes/home.js의 passport.authenticate의 succssRedirect route로 redirect
4. 로그인 된 이후 모든 request가 config/passport.js의 deserialize코드를 거치게 됨
'WEB > Node' 카테고리의 다른 글
게시판 만들기 - Post-User 관계 만들기 (1) 게시물에 작성자 만들기 (0) | 2021.12.19 |
---|---|
게시판 만들기 - post error (0) | 2021.12.19 |
게시판 만들기 / User 생성, 수정 시 error 처리 (2) (0) | 2021.12.01 |
게시판 만들기 / User 생성, 수정 시 error 처리 (1) (0) | 2021.12.01 |
게시판 만들기 / password 암호화 -bcrypt (0) | 2021.11.17 |