본문 바로가기

Node

Passport 모듈로 회원가입 및 로그인 하기 (모듈화 적용 X)

노드에서 사용할 수 있는 사용자 인증 모듈

① 로컬인증 : 데이터베이스에 저장된 사용자 정보와 비교
② OAuth인증 : 페이스북, 구글, 네이버, 카카오 계정으로 로그인  - key값을 받아야 하니깐 eclipse에서 쓰지말고 쓰는법은 각 홈페이지 API 참조하는게 나음

 

사용자 인증 처리를 위한 필수 모듈 

npm install passport --save
npm install passport-local --save  - 로컬 인증 기능 (데이터베이스에 저장된 사용자 정보와 비교)
npm install connect-flash --save  - 요청객체에 메세지를 넣어둘 수 있는 기능, 다른 함수나 뷰 템플릿 처리 함수에 메세지를 전달, 사용자에게 메세지 전달 

 

 

* node_modules에 passport-strategy 모듈도 함께 설치되는데 이것은 OAuth 인증 (카카오,네이버,...) 기능이다


<사용자 로그인과 회원가입 처리 과정>

클라이언트 요청 웹 서버 뷰 템플릿
홈화면 조회 / 홈화면 index.ejs
회원가입 조회 /signup (get) 회원가입화면 signup.ejs
회원가입 요청 /signup (post) 회원가입처리 (함수로 처리)
로그인화면 조회 /login(get) 로그인화면 login.ejs
로그인 요청 /login(post) 로그인처리 (함수로 처리)
사용자 프로필 /profile(get) 프로필화면 profile.ejs
로그아웃 요청 /logout(get) 로그아웃처리 (함수로 처리)

 

개발환경 - os: Window8.1 64x /  프로세서: Intel(R) Core(TM) i3-4020Y CPU @ 1.50GHz / RAM: 8.0GB

개발도구 - IDE: eclipse 4.4.2 (32bit) / DBMS: Oracle11g / 컴파일러: jdk 1.8.0 (java version)

개발언어 - Java, HTML5, CSS3, JavaScript(Node.js), SQL, jQuery, BootStrap

 

 


앞에서 연습했던 ModuleExe를 복사해와서 PassportExe 패키지를 생성한다(package.json 가서 "name" 바꿔줄것)

ModuleExe 패키지와의 차이점은 view단을 전부 ejs로 만들 것

 

 

차이점①

view단을 ejs로만 만들거기 때문에 router/user.js가 필요 없게됨

-> 이 말은 config.js 에 있던 라우터 정보도 필요 없게 된 것 

-> url별 view단 매핑정보를 app2.js 에 기재해야 함

 

이전 config.js

		//라우터 정보
		route_info:[
		        {file:"./user", path:"/process/login", 
		        	method:"login", type:"post"},
		        {file:"./user", path:"/process/addUser", 
			       	method:"addUser", type:"post"},
			    {file:"./user", path:"/process/listUser", 
				    method:"listUser", type:"post"}		        
			       	]		

변화 후 config.js

		//라우터 정보
		route_info:[
		            
		]

 

차이점②

인증방식을 email로 이용하기 위해 database/user_schema.js에서 id에 접근하던걸 email로 고쳐준다

Schema.createSchema = function(mongoose) {	

	UserSchema = mongoose.Schema({
		email: {type:String, "default":""},

Schema가 바뀌면 테이블도 바뀌어야 하므로 config.js가서 db_schemas부분도 수정 (collection 부분)

	//db참조
		db_schemas:[
		   {file:"./user_schema",collection:"users4",
			schemaName:"UserSchema",modelName:"UserModel"}       
		] 

 

디자인 프레임워크 가져오기 

 

1. 아이콘 무료 사이트 - https://fontawesome.com/

 

Font Awesome

 

fontawesome.com

쓰고싶은 아이콘 클릭, Start Using This Icon 클릭

이것만 copy해 오면 됨

(주의) i쪽 자체가 icon이니까 그 안에다가 카드입니다 이런 문구 쓰는거 아님!

<i class="fas fa-address-card"></i>카드입니다 (O)

 

2. 부트스트랩

https://getbootstrap.com/

 

Bootstrap

The most popular HTML, CSS, and JS library in the world.

getbootstrap.com

 

CDN 선언 (버전은 상이할 수 있음)

<link rel="stylesheet"  href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css">

 


passport 설정

// passport 모듈
var passport = require("passport"); // 인증을 위한 필수 모듈
var flash = require("connect-flash"); // 사용자한테 메세지를 전달하는 모듈
//Passport 사용 설정 (반드시 세션설정 밑에 기술)
//Passport 의 두개의 함수를 호출했을 때 반환하는 객체를 미들웨어로 사용
app.use(passport.initialize());
app.use(passport.session());
app.use(flash());

 

**원래 router/user.js 에 있던 로그인, 회원가입 기능 등등을 app2.js에다가 써주려고 한다**

로그인 관련 라우팅 함수 설정

post 방식으로 요청할 때는 passport 객체의 authenticate() 메소드를 호출하여 패스포트 모듈에서 사용자 인증을 처리하도록 넘겨줌

//router4. 로그인화면조회
router.route("/login").get(function(req,res){
	res.render("login.ejs",
			{message:req.flash("loginMessage")});
	
});

//router5. 로그인요청 (결과페이지)
// 사용자 인증 - POST로 요청받으면 패스포트를 이용해 인증함
// 인증 실패 시 검증 콜백에서 설정한 flash 메시지가 응답 페이지에 전달되도록 함
router.route("/login")
	.post(passport.authenticate("local-login",{
	
	successRedirect : "/profile",
	failureRedirect : "/login",
	failureFlash : true 
	
		}));

 

 

login.ejs

<!-- flash 메세지 "등록된 계정이 없습니다" - 이 메세지 띄워줄것임 --> 
		<%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="email"/>
			</div>
			
			<div class="form-group">
			<label>비밀번호</label>
			<input type="password" class="form-control" name="pwd"/>
			</div>
		
			<button type="submit" class="btn btn-warning btn-lg">로그인</button>		
		</form>

 

 

메소드1. Passport 로그인 설정 (app2.js)

usernameField, passwordField의 value는 signup.ejs의 name 값과 동일해야 한다

이 값으로 데이터베이스의 값과 비교해서 인증 절차를 진행하게 되는데,

인증이 성공한 경우는 done(null, 유저 정보 객체)를 넘기게 된다

*

use(이름,인증방식객체) : 이름은 함수 구별 용도, 사용자 정의
done() : 예약어 
usernameField, passwordField 어떤 폼 필드로부터 아이디와 비밀번호를 전달받을 지 설정하는 옵션
passReqToCallback : 인증을 수행하는 인증 함수로 HTTP request를 그대로  전달할지 여부를 결정

// Passport Strategy 설정 (어떤 인증방식 사용할건지 객체 생성)
var LocalStrategy = require("passport-local").Strategy; 
// 'passport-local'모듈의 Strategy라는 메소드 가져온것

passport.use("local-login", 
		new LocalStrategy({usernameField : "email",passwordField : "pwd", 
			passReqToCallback : true}, function(req,email,pwd,done) {
				// 콜백함수의 첫번째 파라미터로 req 객체가 전달됨
		console.log("passport의 local-login 호출");
		console.log(email + ":" + pwd);
		
		// 데이터베이스 객체 생성
		var database = app.get("database");
		
		database.UserModel.findOne({"email":email}, function(err,user){
			
			//조회하다가 에러 발생한 경우
			if(err) {return done(err);} // 원래는 throw err; 이렇게 처리했음
			
			// 1. 조회는 했는데 해당 이메일로 등록된 사용자가 없는 경우
			if(!user) {
				console.log("이메일이 일치하지 않음");
				
				//검증 콜백에서 두번째 파라미터를 false로 해서 인증 실패한것으로 처리			
				return done(null,false,
						req.flash("loginMessage","등록된 계정이 없습니다")); 
				// flash : 메세지 넘겨주는 애 
			}			
			
			// 2. 조회 했고 해당 이메일로 등록된 사용자를 찾은 경우
			
			// 2-1.  비밀번호 비교
			var authenticated =
				user.authenticate(pwd,
						user._doc.salt,user._doc.hashed_pwd);
			
			if(!authenticated) {
				
				console.log("비밀번호가 일치하지 않음");
				
				return done(null,false,
						req.flash("loginMessage","비밀번호가 일치하지 않음")); 
			}
			
			// 2-2. 비밀번호까지 전부 일치한 경우
			console.log("계정과 비밀번호가 일치함");
			
			return done(null,user);
			
		});
		
	}

)); 

 

 

passport 객체의 serializeUser()와 deserializeUser() 메소드

 - serializeUser() : 메소드를 호출하면서 등록한 콜백 함수는 사용자 인증이 성공적으로 진행되었을 때 호출됨

 - deserializeUser() : 사용자 인증 이후 사용자 요청이 들어올 때마다 호출

//사용자 인증 시 호출
//사용자 정보를 이용해서 세션을 만듬 
passport.serializeUser (function(user,done){
	
	console.log("serializeUser 호출");
	
	//여기 인증 콜백에서 넘겨주는 user 객체의 정보를 이용해서 세션을 생성 
	done(null,user); // 에러 없으니깐 null, 그리고 user넘겨주면 됨
	
});

 

//사용자 인증 이후 사용자가 요청할 때마다 호출하는 부분임 
// 사용자 정보를 이용해서 세션을 만듬
passport.deserializeUser (function(user,done){
 // 매개변수 user -> 사용자 인증 성공 시 serializeUser 메소드를 이용해 만들었던 세션 정보가
 // 파라미터로 넘어온 것임 (serizlizeUser의 done의 인자 user를 받은 것)
	
	//사용자 정보 중에서 id나 email만 있는 경우 사용자 정보 조회가 필요
    // 여기에서는 파라미터로 받은 user를 별도로 처리하지 않고 그대로 넘겨줌
	//두번째 파라미터로 지정한 사용자 정보(user)가 req.user가 됨
	done(null,user); 	
});

 

 

signup.ejs

		<%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="email"/>
			</div>
		
			<div class="form-group">
			<label>비밀번호</label>
			<input type="password" class="form-control" name="pwd"/>
			</div>
			
			<div class="form-group">
			<label>이름</label>
			<input type="text" class="form-control" name="name"/>
			</div>			
		
			<button type="submit" class="btn btn-warning btn-lg">회원가입</button>		
		</form>

 

회원가입 관련 라우팅 함수 설정

//router2. 회원가입조회 
router.route("/signup").get(function(req,res){
	
	res.render("signup.ejs",
			{message:req.flash("signupMessage")});
	
});

//router3. 회원가입요청 (결과페이지)
router.route("/signup")
	.post(passport.authenticate("local-signup",{
	
	successRedirect : "/profile",
	failureRedirect : "/signup",
	failureFlash : true 
	// 패스포트로 인증하는 과정에서 오류 발생시 플래시 메시지가 오류로 전달된다.	
		}));

 

 

메소드2. Passport 회원가입 설정

passport.use("local-signup", new LocalStrategy({
		usernameField : "email",
		passwordField : "pwd",
		passReqToCallback : true
		}, function(req,email,pwd,done) {
			
			// 사용자가 입력한 name, pwd를 받는다 
			var paramName = req.body.name || req.query.name;
		
			console.log("passport의 local-signup 호출");
			console.log(email + ":" + pwd);
			
			process.nextTick(function(){
				
				// 데이터베이스 객체 생성
				var database = app.get("database");
				database.UserModel
				.findOne({"email":email}, function(err,user){
					
					//조회하다가 에러 발생한 경우
					if(err) {return done(err);} 
					
					// 1. 이미 등록된 사용자가 있는경우 
					if(user) {
						console.log("이미 이메일 계정이 있습니다");						
						return done(null,false,
								req.flash("signupMessage","이미 이메일 계정이 있습니다"));  
					} else {
					
					// 2. 등록된 사용자가 없는 경우	
						console.log("등록된 사용자가 없으므로 회원가입 진행");					
						
						var user = 
							new database.UserModel({"email":email,
								"pwd":pwd,
								"name":paramName});
						
						user.save(function(err){
						
							if(err) {throw err;}
							
							console.log("사용자 데이터 추가 완료");
							
							return done(null,user); // user만 넘김
						});
						
					} // end...if
			}); // findOne 끝
			
			}); // nextTick 끝
			
		})); // function 끝

 

profile.ejs

			<div class="well">
				<h3><span class="fa fa-user"></span>
				로컬 프로필 정보</h3>
				<br/>
				<p>
				<strong>이메일</strong> : <%=user.email %> <br/><br/>
				<strong>이름</strong> : <%=user.name %> </p>
				<br/>
			</div>

 

 

사용자 프로필 관련 라우팅 함수 설정

//router6. 사용자프로필
router.route("/profile").get(function(req,res){
	
	// (1) 인증이 안된 경우 (= 로그인 안된 경우)
	if(!req.user) {		
		
		console.log("사용자 인증이 안된 상태임");
		res.redirect("/"); // 홈화면으로
		return;
		
	}
	
	// (2) 인증이 된 경우 (= 로그인 된 경우)
	console.log("사용자 인증이 된 상태임");
	
	// js에서는 배열 타입을 별도로 제공하지 않는다
	// 배열은 객체 타입 (object) 로 처리되며 배열인지 아닌지 확인할 수 있는 방법이
	// Array.isArray 이다
	if(Array.isArray(req.user)) {
		
		res.render("profile.ejs",{user: req.user[0]._doc});
		
	} else {
		
		res.render("profile.ejs",{user: req.user});
		
	} 
	
});

 

로그아웃 관련 라우팅 함수 설정

//router7. 로그아웃 요청
router.route("/logout").get(function(req,res){
	
	req.logout();
	res.redirect("/");
	
});

 

결과화면