Download presentation
Presentation is loading. Please wait.
1
패스포트로 사용자 인증하기 9장 Do it! Node.js 프로그래밍 이지스퍼블리싱 제공 강의 교안 2017/03
○ 본 강의 자료는 이지스퍼블리싱에서 제공하는 강의 교안입니다. ○ 본 강의 교안은 아래 출판 서적의 내용을 기준으로 구성되었습니다. 또한 다수의 기타 서적이나 사이트를 참조하였습니다. 레퍼런스를 참조하십시오. 2017, 정재곤, “Do it! Node.js 프로그래밍 (개정판)”, 이지스퍼블리싱 - 강의 교안에 사용된 화면 캡처나 실습 자료의 경우에는 문서 업데이트에 따라 변경될 수 있습니다.
2
어떻게 하면 사용자 인증을 간단하게 할 수 있을까?
강의 주제 및 목차 강의 주제 어떻게 하면 사용자 인증을 간단하게 할 수 있을까? 목 차 1 패스포트로 로그인하기 2 패스포트 관련 코드를 모듈화하기 3 페이스북으로 로그인하기
3
1. 패스포트로 로그인하기 난이도 소요시간 30분
4
패스포트란? 노드에서 사용할 수 있는 사용자 인증 모듈 인증 방식은 Strategy로 만들어져 있음
5
패스포트의 기본 사용방법 자주 사용되는 코드 형태 local 은 스트래티지의 이름
router.route('/login').post(passport.authenticate('local', { successRedirect: '/', failureRedirect: '/login' } )); route.route('/login').post(passport.authenticate('local'), function(req, res) { // 인증 성공시 호출됨., 'req.user'는 인증된 사용자에 대한 정보임 res.redirect('/users/' + req.user.username); } );
6
authenticate 호출 결과에 따른 처리
7
플래시 메시지 사용하기 connect-flash 모듈 사용
8
플래시 메시지 사용하기 오류 메시지를 플래시 메시지로 전달하기
router.route('/login').post(passport.authenticate('local', { successRedirect: '/', failureRedirect: '/login', failureFlash: true } ));
9
커스텀 콜백 사용하기 라우팅 함수를 등록하고 그 안에서 인증 진행하는 방식
router.route('/login').get(function(req, res, next) { passport.authenticate('local', function(err, user, info) { if (err) { return next(err); } if (!user) { return res.redirect('/login'); } // 패스포트 인증 결과에 따라 로그인 진행 req.login(user, function(err) { return res.redirect('/users/' + user.username); }); })(req, res, next);
10
스트래티지 설정하기 인증 방식을 결정하는 것이 스트래티지 var passport = require('passport')
, LocalStrategy = require('passport-local').Strategy; passport.use(new LocalStrategy( function(username, password, done) { UserModel.findOne({ username: username }, function (err, user) { if (err) { return done(err); } if (!user) { return done(null, false, { message: 'Incorrect username.' }); } if (!user.validPassword(password)) { return done(null, false, { message: 'Incorrect password.' }); return done(null, user); }); ));
11
스트래티지 설정과 authenticate의 관계
12
검증 콜백의 사용 인증 정보를 가지고 사용자를 찾아내는 역할 로컬 인증 방식에서 검증 콜백의 사용 방식
스트래티지 안에서 Done() 메소드가 어떻게 호출되는가에 따라 authenticate 의 인증 결과가 달라짐
13
2. 로컬 인증하기 난이도 소요시간 20분
14
로컬 인증 사용하기 passport, passport-local, connect-flash 모듈 사용
% npm install passport --save % npm install passport-local --save % npm install connect-flash --save var passport = require('passport'); var flash = require('connect-flash'); … app.use(passport.initialize()); app.use(passport.session()); app.use(flash());
15
데이터베이스 스키마 수정 이메일 인증이 가능하도록 데이터베이스 스키마 수정
var UserSchema = mongoose.Schema({ {type : String, 'default' : ''} , hashed_password : {type : String, required : true, 'default' : ''} , name : {type : String, index : 'hashed', 'default' : ''} , salt : {type : String, required : true} , created_at : {type : Date, index : {unique : false}, 'default' : Date.now} , updated_at : {type : Date, index : {unique : false}, 'default' : Date.now} });
16
속성의 유효성 검증 email 속성과 hashed_password 속성에 값이 없는지 확인 // 입력된 칼럼 값이 있는지 확인
UserSchema.path(' ').validate(function( ) { return .length; }, ' 칼럼의 값이 없습니다.'); UserSchema.path('hashed_password').validate(function(hashed_password) { return hashed_password.length; }, 'hashed_password 칼럼의 값이 없습니다.');
17
모델 객체에서 사용할 수 있는 메소드 정의 static() 메소드를 이용해 모델 객체에서 사용할 수 있는 메소드 정의
// 모델 객체에서 사용할 수 있는 메소드 정의 UserSchema.static('findBy ', function( , callback) { return this.find({ }, callback); }); UserSchema.static('findAll', function(callback) { return this.find({ }, callback);
18
설정에서 스키마 등록 부분 수정 config.js 파일을 열고 스키마 등록 부분 수정 module.exports = {
server_port : 3000, db_url : 'mongodb://localhost:27017/local', db_schemas : [ {file : './user_schema', collection : 'users5', schemaName : 'UserSchema', modelName : 'UserModel'} ], route_info : [ ] }
19
로컬 패스포트 인증을 처리할 함수 등록 local-login 이름으로 LocalStrategy 객체 등록
var LocalStrategy = require('passport-local').Strategy; // 패스포트 로그인 설정 passport.use('local-login', new LocalStrategy({ usernameField : ' ', passwordField : 'password', passReqToCallback : true }, function(req, , password, done) { console.log('passport의 local-login 호출됨 : ' + + ', ' + password); var database = app.get('database'); database.UserModel.findOne({' ' : }, function(err, user) { if(err) {return done(err);} // 등록된 사용자가 없는 경우 if(!user) { console.log('계정이 일치하지 않음.'); return done(null, false, req.flash('loginMessage', '등록된 계정이 없습니다.')); }
20
로컬 패스포트 인증을 처리할 함수 등록 local-login 이름으로 LocalStrategy 객체 등록
// 비밀번호를 비교하여 맞지 않는 경우 var authenticated = user.authenticate(password, user._doc.salt, user._doc.hashed_password); if (!authenticated) { console.log('비밀번호 일치하지 않음.'); return done(null, false, req.flash('loginMessage', '비밀번호가 일치하지 않습니다.')); } // 정상인 경우 console.log('계정과 비밀번호가 일치함.'); return done(null, user); }); }));
21
클라이언트의 요청 파라미터 매핑 username과 password라는 이름 대신 다른 이름으로 지정 가능
22
회원가입 기능을 처리할 함수 등록 local-signup 이름으로 LocalStrategy 객체 등록
// 패스포트 회원가입 설정 passport.use('local-signup', new LocalStrategy({ usernameField : ' ', passwordField : 'password', passReqToCallback : true }, function(req, , password, done) { var paramName = req.param('name'); console.log('passport의 local-signup 호출됨 : ' + + ', ' + password + ', ' + paramName); // User.findOne이 blocking되므로 async 방식으로 변경할 수도 있음 process.nextTick(function() { var database = app.get('database'); database.UserModel.findOne({' ' : }, function(err, user) { // 오류가 발생하면 if(err) { return done(err); }
23
회원가입 기능을 처리할 함수 등록 local-signup 이름으로 LocalStrategy 객체 등록
// 기존에 이메일이 있다면 if(user) { console.log('기존에 계정이 있음.'); return done(null, false, req.flash('signupMessage', '계정이 이미 있습니다.')); } else { // 모델 인스턴스 객체 만들어 저장 var user = new database.UserModel({' ' : , 'password' : password, 'name' : paramName}); user.save(function(err) { if(err) {throw err;} console.log("사용자 데이터 추가함."); return done(null, user); }); }
24
패스포트를 위한 메소드 추가 serializeUser() 메소드는 사용자 인증이 성공적으로 진행되었을 때 호출되는 함수 등록
deserializeUser() 메소드는 인증 이후 사용자 요청이 들어올 때마다 호출되는 함수 등록 // 사용자 인증에 성공했을 때 호출 passport.serializeUser(function(user, done) { console.log('serializeUser() 호출됨.'); console.dir(user); done(null, user); }); // 사용자 인증 이후 사용자 요청이 있을 때마다 호출 passport.deserializeUser(function(user, done) { console.log('deserializeUser() 호출됨.');
25
메소드가 호출되는 방식 상황에 맞게 serializeUser()와 deserializeUser() 메소드가 호출됨
26
로그인과 회원가입을 위한 라우팅 함수 등록 사용자 로그인과 회원가입 처리 과정
27
사용자 요청 패스 추가 login, signup, profile, logout 기능을 요청 패스로 추가 요청 패스 요청 방식
설명 / get 홈 화면 조회 /login 로그인 화면 조회 post 패스포트로 사용자 인증을 처리하는 함수 호출 /signup 회원가입 화면 조회 패스포트로 회원 가입을 처리하는 함수 호출 /profile 사용자 프로필 화면 조회 /logout 로그아웃을 처리하는 함수 호출
28
로그인 요청 처리 login 요청에 대해 passport 객체의 authenticate() 메소드 호출하도록 등록 authenticate() 메소드에서는 ‘local-login’으로 등록된 함수 사용 router.route('/login').post(passport.authenticate('local-login', { successRedirect : '/profile', failureRedirect : '/login', failureFlash : true })); … router.route('/signup').post(passport.authenticate('local-signup', { failureRedirect : '/signup',
29
프로필 링크 isLoggedIn() 메소드를 호출하여 로그인 여부를 먼저 확인
router.route('/profile').post(function(req, res) { console.log('/profile 패스 요청됨.'); if (req.isAuthenticated()) { console.dir(req.user); if(Array.isArray(req.user)) { res.render('profile.ejs', {user : req.user[0]._doc}); } else { res.render('profile.ejs', {user : req.user}); } } else (req.isAuthenticated()) { res.redirect('/'); });
30
로그아웃 로그아웃 시에는 req 객체의 logout() 메소드 호출 // 로그아웃
router.route('/logout‘).get(function(req, res) { console.log('/logout 패스 요청됨.'); req.logout(); res.redirect('/'); });
31
부트스트랩으로 뷰 템플릿 만들기 부트스트랩 사용하여 웹페이지 작성 (http://getbootstrap.com)
네 개의 뷰 템플릿 작성 템플릿 이름 설명 index.ejs 홈 화면을 위한 뷰 템플릿 login.ejs 로그인 화면을 위한 뷰 템플릿 signup.ejs 회원가입 화면을 위한 뷰 템플릿 profile.ejs 사용자 프로필 화면을 위한 뷰 템플릿
32
index.ejs 템플릿 파일 첫 화면을 위한 템플릿 파일 <div class = "container">
<div class = "jumbotron text-center"> <h1><span class = "fa fa-shopping-cart"></span> 쇼핑몰 홈</h1> <br> <p>로그인하세요.<p> <p>계정이 없으시면 회원가입하세요.</p> <a href = "/login" class = "btn btn-default"> <span class = "fa fa-user"></span>로그인 </a> <a href = "/signup" class = "btn btn-default"> <span class = "fa fa-user"></span>회원가입 </div>
33
login.ejs 템플릿 파일 로그인을 위한 템플릿 파일 <div class = "container">
<div class = "col-sm-6 col-sm-offset-3"> <h1><span class = "fa fa-sign-in"></span> 로그인</h1> <!-- 인증 처리 후 메시지가 있으면 메시지 표시 --> <% if(message.length > 0) { %> <div class = "alert alert-danger"><% = message %></div> <% } %> <form action = "/login" method = "post" > <div class = "form-group"> <label>이메일</label> <input type = "text" class = "form-control" name = " "> </div>
34
login.ejs 템플릿 파일 로그인을 위한 템플릿 파일 <div class = "form-group">
<label>비밀번호</label> <input type = "password" class = "form-control" name = "password"> </div> <button type = "submit" class = "btn btn-warning btn-lg">로그인</button> </form> <hr> <p>계정이 없으세요? <a href = "/signup">회원가입하기</a></p> <p><a href = "/">홈으로</a>.</p>
35
signup.ejs 템플릿 파일 회원가입을 위한 템플릿 파일 <div class = "container">
<div class = "col-sm-6 col-sm-offset-3"> <h1><span class = "fa fa-sign-in"></span> 회원가입</h1> <!-- 인증 처리 후 메시지가 있으면 메시지 표시 --> <% if(message.length > 0) { %> <div class = "alert alert-danger"><% = message %></div> <% } %> <form action = "/signup" method = "post" > <div class = "form-group"> <label>이메일</label> <input type = "text" class = "form-control" name = " "> </div>
36
profile.ejs 템플릿 파일 프로필 표시를 위한 템플릿 파일 <div class = "container">
<div class = "page-header text-center"> <h1><span class = "fa fa-anchor"></span> 사용자 프로필</h1> <a href = "/logout" class = "btn btn-default btn-sm">로그아웃</a> </div> <br> <div class = "row"> <div class = "col-sm-6"> <div class = "well"> <h3><span class = "fa fa-user"></span> 로컬 프로필 정보</h3> <p> <strong>이름</strong> : <% = user.name %><br><br> <strong>이메일</strong> : <% = user. %> </p>
37
웹브라우저에서 확인 홈 화면, 회원가입 화면, 로그인 화면, 프로필 화면 ▶ http : //localhost : 3000/
38
3. 패스포트 관련 코드를 모듈화하기 난이도 소요시간 20분
39
패스포트 관련 코드를 모듈화하는 과정 PassportExample 프로젝트를 복사하여 PassportExample2 프로젝트로 만듬 패스포트 설정 파일을 별도로 만들어 관리
40
패스포트 설정 파일 생성 config 폴더 안에 passport.js 파일 만들기
var local_login = require('./passport/local_login'); var local_signup = require('./passport/local_signup'); module.exports = function (app, passport) { console.log('config/passport 호출됨.'); // 사용자 인증에 성공했을 때 호출 passport.serializeUser(function(user, done) { console.log('serializeUser() 호출됨.'); console.dir(user); done(null, user); }); // 사용자 인증 이후 사용자 요청이 있을 때마다 호출 passport.deserializeUser(function(user, done) { console.log('deserializeUser() 호출됨.'); passport.use('local-login', local_login); passport.use('local-signup', local_signup); };
41
로컬 로그인 Strategy 파일 분리 passport 폴더 안에 local_login.js 파일 만들고 로컬 로그인을 위한 Strategy 파일 분리 var LocalStrategy = require('passport-local').Strategy; module.exports = new LocalStrategy({ usernameField : ' ', passwordField : 'password', passReqToCallback : true }, function(req, , password, done) { console.log('passport의 local-login 호출됨 : ' + + ', ' + password); …….
42
로컬 회원가입 Strategy 파일 분리 passport 폴더 안에 local_signup.js 파일 만들고 로컬 회원가입을 위한 Strategy 파일 분리 ar LocalStrategy = require('passport-local').Strategy; module.exports = new LocalStrategy({ usernameField : ' ', passwordField : 'password', passReqToCallback : true }, function(req, , password, done) { var paramName = req.param('name'); console.log('passport의 local-signup 호출됨 : ' + + ', ' + password + ', ' + paramName); …….
43
패스포트 설정 파일 불러오기 메인 파일에서 passport.js 파일 불러온 후 함수 실행 // 패스포트 설정
var configPassport = require('./config/passport'); configPassport(app, passport); …….
44
라우팅 함수를 위한 모듈 파일 만들기 routes 폴더 안에 user_passport.js 파일을 만들고 라우팅 함수 추가
module.exports = function(app, passport) { // 홈 화면 - 로그인 링크 router.route('/‘).get(function(req, res) { ……. }); // 로그인 폼 링크 router.route('/login‘).get(function(req, res) { var userPassport = require('./routes/user_passport'); userPassport(app, passport); …….
45
4. 페이스북으로 로그인하기 난이도 소요시간 20분
46
페이스북으로 로그인 방식 포털 사이트나 SNS 계정을 사용하는 로그인 방식을 많이 사용 OAuth 인증 기능 사용
47
페이스북 사이트에서 등록 페이스북 개발자 콘솔 창에서 애플리케이션 등록을 해야 사용 가능
▶
48
페이스북 사이트에서 등록 페이스북 개발자 콘솔 창에서 애플리케이션 등록을 해야 사용 가능
49
생성된 정보를 설정에 추가 개발자 콘솔에서 생성한 App ID와 App Secret 정보를 config.js 파일에 추가
module.exports = { server_port : 3000, db_url : 'mongodb://localhost:27017/shopping', db_schemas : [ {file : './user_schema', collection : 'users6', schemaName : 'UserSchema', modelName : 'UserModel'} ], route_info : [ facebook : { clientID : 'OOOOOOOOOOOOO', clientSecret : 'OOOOOOOOOOOOOOOOOOOO', callbackURL : '/auth/facebook/callback' }, …….
50
패스포트 설정 파일에 추가 및 새로운 파일 생성 passport.js 파일에 추가 …….
var facebook = require('./passport/facebook'); module.exports = function (app, passport) { passport.use('facebook', facebook(app, passport)); }
51
페이스북을 위한 파일 생성 passport 폴더 안에 facebook.js 파일 생성
var FacebookStrategy = require('passport-facebook').Strategy; var config = require('../config'); module.exports = function(app, passport) { return new FacebookStrategy({ clientID : config.facebook.clientID, clientSecret : config.facebook.clientSecret, callbackURL : config.facebook.callbackURL }, function(accessToken, refreshToken, profile, done) { console.log('passport의 facebook 호출됨.'); console.dir(profile);
52
페이스북을 위한 파일 생성 passport 폴더 안에 facebook.js 파일 생성 var options = {
criteria : {'facebook.id' : profile.id} }; var database = app.get('database'); database.UserModel.load(options, function (err, user) { if(err) return done(err); if(!user) { var user = new database.UserModel({ name : profile.displayName, profile. s[0].value, provider : 'facebook', facebook : profile._json }); user.save(function(err) { if(err) console.log(err); return done(err, user);
53
모듈 설치하고 데이터베이스 스키마 변경 passport-facebook 모듈 설치 필요 스키마 변경 필요
% npm install passport-facebook --save var UserSchema = mongoose.Schema({ {type : String, 'default' : ''} , hashed_password : {type : String, 'default' : ''} , name : {type : String, index : 'hashed', 'default' : ''} , salt : {type : String} , created_at : {type : Date, index : {unique : false}, 'default' : Date.now} , updated_at : {type : Date, index : {unique : false}, 'default' : Date.now} , provider : {type : String, 'default' : ''} , authToken : {type : String, 'default' : ''} , facebook : { } });
54
라우팅 함수 추가 라우팅 함수 만든 후 설정에 추가
module.exports = function(app, passport) { ……. // 패스포트 - 페이스북 인증 라우팅 router.route('/auth/facebook').get(passport.authenticate('facebook', { scope : ' ' }) ); // 패스포트 - 페이스북 인증 콜백 라우팅 router.route('/auth/facebook/callback').get(passport.authenticate('facebook', { successRedirect : '/profile', failureRedirect : '/' }
55
홈 화면을 위한 웹페이지 수정 후 확인 index.ejs 파일의 코드 수정 후 웹브라우저에서 접속하여 확인 …….
<a href = "/auth/facebook" class = "btn btn-primary"> <span class = "fa fa-facebook"></span> 페이스북으로 로그인 </a>
56
프로필 페이지 수정 profile.ejs 파일 수정 …….
<% if(user.provider == undefined || user.provider == '') { %> <h3><span class = "fa fa-user"></span> 로컬 프로필</h3> <br> <p> <strong>이메일</strong> : <% = user. %><br><br> <strong>별명</strong> : <% = user.name %> </p> <% } else if(user.provider == 'facebook') { %> <h3 class = "text-primary"> <span class = "fa fa-facebook"></span> 페이스북 프로필 </h3> <strong>아이디</strong> : <% = user.facebook.id %><br><br> <strong>이메일</strong> : <% = user.facebook. %><br><br> <strong>이름</strong> : <% = user.facebook.name %> <% } %>
57
참고 문헌 [ References] 기본 서적
2017, 정재곤, “Do it! Node.js 프로그래밍 (개정판)”, 이지스퍼블리싱 Node.js Site
Similar presentations