본문 바로가기

Node

Node.js + Oracle 을 이용한 로그인/패스워드 웹서버 구축하기

1. DB 연결자, Router 연결자 - config.js

2. DB 관련
① Schema 정보 (스키마생성&스키마메소드추가) - user_schema.js
② database 객체에 DB정보, Schema정보, Schema Model 정보 추가 - database.js 
(config.js, user_shema.js에 접근하여 모델객체 생성)

3. 라우팅 관련
ⓛ config.js 에 있는 Router 연결자 정보를 이용해 상황별 메소드에 접근 - router_loader.js
② UserModel 객체에 접근하여 결과 페이지 view단을 만듬 - user.js

4. 메인 파일 - app2.js

개발환경 - 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

 

 

<전체 경로>

 

 

 

1. DB 연결자, Router 연결자 - config.js

module.exports = {
		server_port:3000,
		db_url:"mongodb://localhost:27017/shopping",
		
		// 스키마 위치지정, db:user3, 스키마이름, 모델이름
		db_schemas:[
		   {file:"./user_schema",collection:"users3",
			schemaName:"UserSchema",modelName:"UserModel"}         
		], 
        
        
        //라우터 정보
		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"}		        
			       	]
		
}

 

2. DB 관련

① Schema 정보 (스키마생성&스키마메소드추가) - user_schema.js

<내가 정의한 스키마 객체에 메소드를 추가하는 방법>

방법1. static 이용

방법2. 메소드 추가

 

<로그인 인증 메소드 원리>

//인증메소드 (입력된 비밀번호와 비교: true/false)
	UserSchema.method("authenticate",
			function(inputPwd,inSalt,hashed_pwd){ // hashed_pwd: 이미 저장된 pwd 

		if(inSalt) { // 넘어온 key값이 있다면

		return this.encryptPwd(inputPwd,inSalt) 
			=== hashed_pwd;
            
		} else { // 없어도
			return this.encryptPwd(inputPwd) 
			=== this.hashed_pwd;
		}

DB에 저장된 Key값사용자가 입력한 패스워드를 다시 암호화 - encryptPwd() 하여

DB에 저장된 hashed_pwd와 비교한다 (DB에는 123이란 패스워드 데이터가 없고 암호화된 데이터만 저장돼있기 때문)

salt = key 값 != hashed_pwd

 

//Schema 정보 
var crypto = require("crypto");
var Schema = {};

Schema.createSchema = function(mongoose) {	
	
	UserSchema = mongoose.Schema({
		id: {type:String, required:true, unique:true, "default":""}, // default, 없으면 null
		hashed_pwd: {type:String, required:true, unique:true, "default":""}, // 암호화된 패스워드 저장하는 공간, 단순히 123이 저장되는게 아님
		salt: {type:String, required:true}, // 암호화를 할 때 만들어지는 일종의 값 
		// 왜 String인가? crypt : 랜덤 값(key) + 사용자가 입력한 패스워드(ex. 123) 합쳐서 암호화를 시킴
		// 123만 암호화 안되고 반드시 key를 줘야함
		// (salt는 그냥 고정 키값을 줘도 됨. 임의의값 아무거나 있으면 되는것 )
		name: {type:String, index:"hashed","default":""}, // name에 index 추가				
		age : {type:Number,"default":20},
		created_at: {type:Date, index:{unique:false},"default":Date.now}, // 날짜는 중복값이 들어가도 상관 없으니깐
		updated_at: {type:Date, index:{unique:false},"default":Date.now}  // 수정 날짜
		
	});	

	/*
	 * 가상스키마영역, 실제존재하는속성이 아님 
	 */
	UserSchema
	.virtual("pwd") //pwd: 사용자가 넘겨주는 실제 패스워드
	.set(function(pwd) {
		this._pwd = pwd;
		this.salt = this.makeSalt();// 암호화를 하기 위해 key 하나가 만들어짐 (내가 만드는게 아님) => key값 생성
		//salt라는 속성이 만들어지면서 makeSalt() 메소드를 호출
		// 결과값으로 복잡한 숫자를 돌려받음

		this.hashed_pwd =
			this.encryptPwd(pwd); // 위의 salt 키값을 가지고 내가 입력한 pwd를 암호화시켜서 hashed_pwd에 넣는다
	})
	.get(function(){
		return this._pwd;
	});

	
//	<Schema> 객체에 메소드를 추가 (방법1. static, 방법2. method)
//	스키마 객체의 모델 인스턴스 객체에 메소드 추가 - 방법2 이용
	
//	총 method 3개 만듬 - encryptPwd, makeSalt, authenticate
	
	
	//입력된 패스워드와 키를 합쳐서 암호화시키는 작업을 하는 메소드
	UserSchema.method("encryptPwd",function(inputPwd,inSalt){ 
		// plain = text(일반문자) 
		if(inSalt) { // inSalt가 있으면, 합쳐서 암호화시켜라
			return crypto.createHmac("sha1",inSalt) // insalt와 inputpwd를 합쳐서 암호화
			.update(inputPwd).digest("hex");
			// sha(쉬바) - 암호화하는방법, hex(헥사)

		} else {				
			return crypto.createHmac("sha1",this.salt) // inSalt가 없어도, 합쳐서 암호화
			.update(inputPwd).digest("hex");
		}

	});

	//salt값 만드는 메소드
	UserSchema.method("makeSalt",function(){

		console.log("VOF- "+new Date().valueOf());
		console.log("MATH- " + Math.random());

		return Math.round((new Date().valueOf() * Math.random())) + "";

	});

	//인증메소드 (입력된 비밀번호와 비교: true/false)
	UserSchema.method("authenticate",
			function(inputPwd,inSalt,hashed_pwd){ // hashed_pwd: 이미 저장된 pwd 

		if(inSalt) { // 넘어온 key값이 있다면
			
           return this.encryptPwd(inputPwd,inSalt) 
			=== hashed_pwd;

		} else {

			return this.encryptPwd(inputPwd) 
			=== this.hashed_pwd;

		}


	});


	//<Schema> 객체에 메소드를 추가 (방법1. static 이용)	
	//	로그인 할 때 사용
	UserSchema.static("findById",function(id,callback){
		return this.find({id:id},callback);
	});
	
	// 전체 사용자 조회할 때 사용
	UserSchema.static("findAll",function(callback){
		return this.find({},callback);
	});		

	console.log("UserSchema 정의 함!!");
	
	return UserSchema;

};


module.exports = Schema; 
// 위의 코드를 외부에서 사용할 수 있게끔 해주는것임
// 반드시 있어야함


 

② database 객체에 DB정보, Schema정보, Schema Model 정보 추가 - database.js  

/**
 * http://usejsdoc.org/
 */

var mongoose = require("mongoose");

// database 객체에 db, schema, model 정보 추가
var database = {};

// 이전 예제에서 만든 init안쓰고, 여기서 새로 생성
// 초기화를 위해 호출하는 함수 (app:express 서버객체)
database.init = function(app,config) {	
	connect(app,config);	
};

// 데이터베이스에 연결하고 응답 객체의 속성으로 db객체 추가
function connect(app,config) {
	
	// 몽구스로 데이터베이스 연결
	mongoose.connect(config.db_url);	
	database = mongoose.connection; 
	
	database.on("error",
			console.error.bind(console,"몽구스 연결 에러.."));	

	database.on("open",function(){

		console.log("데이터 베이스에 연결됨 " + config.db_url);

		//user 스키마 및 모델 객체 생성
		createUserSchema(app,config);		

	});
	
	database.on("disconnected",function(){
		console.log("연결이 끊어졌습니다, 5초후 재연결 합니다");

		setInterval(connectDB,5000); // 5초 후 다시 연결
	});


}


function createUserSchema(app,config) {
	
	var schemaLen = config.db_schemas.length; //4개
	
	for(var i=0; i<schemaLen; i++) {
		
		var curItem = config.db_schemas[i];
		
		// 모듈(config.js)에서 모듈(user_schema.js)을 불러온 후 
		// createSchema()함수 호출 
		var curSchema = 
			require(curItem.file).createSchema(mongoose);		
		// =>결과적으로 user_schema.js의 createSchema를 불러온 것
				
		// 그렇게 만든 스키마를 Model에 적용 
		var curModel = 
			mongoose.model(curItem.collection,curSchema); 
		//collection = 즉 user3 테이블을 가져와서 모델 생성
				
		// database 객체에 속성으로 스키마, 모델 추가
		database[curItem.schemaName] = curSchema;
		database[curItem.modelName] = curModel;
		
	}
	
	app.set("database",database); // db 정보 + 스키마정보 + 모델정보
	
}

module.exports = database;

 

3. 라우팅 관련 
ⓛ config.js 에 있는 Router 연결자 정보를 이용해 상황별 메소드에 접근 - router_loader.js

var routerLoader ={};

var config = require("../config");

routerLoader.init = function(app,router) {
	console.log("routerLoader.init 호출됨");	
	return initRouter(app,router);
};

function initRouter(app,router) {
	
	var infoLen = config.route_info.length; 

	for(var i=0; i<infoLen; i++) {

		var curItem = config.route_info[i];

		var curModule = require(curItem.file); // user.js 를 받아옴	
	
		// 라우팅 처리
		if(curItem.type=="get") {
			//router.route("/process/login").get(login); 원래 이걸 읽는건데 이걸 변수로 변환하면
			router.route(curItem.path).get(curModule[curItem.method]); 
			// curItem.method는 login을 뜻하니깐
		} else if(curItem.type=="post") {
			
			router.route(curItem.path).post(curModule[curItem.method]);
		} 

	}
	
	//미들웨어등록
	app.use("/",router); 

}

// 이렇게 변수화 처리함으로써 
// router.route("/process/login").post(user.login); 
// 이렇게 여러개 일일이 쓰던걸 간편하게 모듈화 시킨것임

module.exports = routerLoader;

 

② UserModel 객체에 접근하여 결과 페이지 view단을 만듬 - user.js

addUser, authUser - 이 파일에서 내부적으로 사용하는 함수 - 외부에서 사용하는게 아니므로 빼낼 필요가 없다 
login, addUsers, listUser - 이 3개 메소드만 다른 파일에서 접근할 수 있게 module.exports 시켜주면 된다

//데이터베이스 객체, 스키마 객체, 모델 객체를 user모듈에서 사용할 수 있도록 전달
var database;
var UserSchema;
var UserModel;

var login = function(req,res) {

	console.log("user.js의 login 호출됨...");

	var paramId = req.body.id || req.query.id;
	var paramPwd = req.body.pwd || req.query.pwd;

	var database = req.app.get("database");

	if(database){

		authUser(database, paramId, paramPwd, 
				function(err,result){
			
			if(err) {

				res.writeHead("200",
						{"Content-Type":"text/html;charset=utf-8"});
				res.write("<meta name='viewport' content='width=device-width, height=device-height, initial-scale=1'/>");
				res.write("<h1>로그인 에러!</h1>");
				res.end();
				return;
			}

			// 조회가 제대로 된 경우
			if(result){

				var userName = result[0].name;

				console.log ("조회됐습니다");
				res.writeHead("200",
						{"Content-Type":"text/html;charset=utf-8"});
				res.write("<meta name='viewport' content='width=device-width, height=device-height, initial-scale=1'/>");
				res.write("<h1>로그인성공!</h1>");
				res.write("<div>아이디: " + paramId + "</div>");
				res.write("<div>이름: " + userName + "</div>");
				res.write("<br/><br/><a href='/public1/login.html'>" + "다시입력</a>");
				res.end();


			} else { // 조회된 데이터가 없는경우 

				console.log ("조회안됐습니다");
				res.writeHead("200",
						{"Content-Type":"text/html;charset=utf-8"});
				res.write("<meta name='viewport' content='width=device-width, height=device-height, initial-scale=1'/>");
				res.write("<meta name='viewport' content='width=device-width, height=device-height, initial-scale=1'/>");
				res.write("<h1>로그인실패!</h1>");
				res.write("<div>아이디와 비밀번호를 다시 확인하세요</div>"); // 문자 div없이 그냥쓰면 텍스트처리되어버림
				res.write("<br/><br/><a href='/public1/login.html'>" + "다시입력</a>");
				res.end();

			}

		});

	} 
	
	else { // db연결 실패 시 

		res.writeHead("200",
				{"Content-Type":"text/html;charset=utf-8"});
		res.write("<meta name='viewport' content='width=device-width, height=device-height, initial-scale=1'/>");
		res.write("<div>database연결하지못했습니다</div>")
		res.write("<h1>db연결 실패!</h1>");
		res.write("<br/><br/><a href='/public1/login.html'>" + "다시입력</a>");
		res.end();
	}

};


var addUsers =  function(req,res) {

	var paramId = req.body.id || req.query.id;
	var paramPwd = req.body.pwd || req.query.pwd;
	var paramName = req.body.name || req.query.name;

	var database = req.app.get("database");

	// database가 연결 되었으면,
	if(database){

		console.log("db연결성공!");

		addUser(database,paramId,paramPwd,paramName,
				function(err,result){

			if(err) {

				res.writeHead("200",
						{"Content-Type":"text/html;charset=utf-8"});
				res.write("<meta name='viewport' content='width=device-width, height=device-height, initial-scale=1'/>");
				res.write("<h1>사용자 추가 에러!</h1>");
				res.end();
				return;
			}

			// 여기서 확실히 입력되었는지 다시 한번 검사
			if(result){
				console.log("사용자 추가 성공!")


				res.writeHead("200",
						{"Content-Type":"text/html;charset=utf-8"});
				res.write("<meta name='viewport' content='width=device-width, height=device-height, initial-scale=1'/>");
				res.write("<h1>사용자 추가 성공</h1>");
				res.write("<br/><br/><a href='/public1/login.html'>" + "로그인</a>");
				res.end();
			} else {
				res.writeHead("200",
						{"Content-Type":"text/html;charset=utf-8"});
				res.write("<meta name='viewport' content='width=device-width, height=device-height, initial-scale=1'/>");
				res.write("<h1>사용자 추가 실패</h1>");
				res.end();
			}

		});

	} else { // db연결 안됨

		res.writeHead("200",
				{"Content-Type":"text/html;charset=utf-8"});
		res.write("<meta name='viewport' content='width=device-width, height=device-height, initial-scale=1'/>");
		res.write("<h1>db연결 실패!</h1>");
		res.write("<br/><br/><a href='/public1/addUser.html'>" + "다시입력</a>");
		res.end();

	}


};



var listUser = function(req,res) {	

	console.log("/process/listUser 호출됨...");

	var database = req.app.get("database");

	if(database){

		//1. 모든 사용자 검색
		// Model 객체 생성
		database.UserModel.findAll(function(err,result){

			// 조회 실패
			if(err) {

				res.writeHead("200",
						{"Content-Type":"text/html;charset=utf-8"});
				res.write("<meta name='viewport' content='width=device-width, height=device-height, initial-scale=1'/>");
				res.write("<h1>사용자 리스트 조회 실패</h1>");
				res.end();
				
				return; // 더이상 실행하면 안되니깐 return
			}		

			// 조회 성공
			if(result){
				res.writeHead("200",
						{"Content-Type":"text/html;charset=utf-8"});
				res.write("<meta name='viewport' content='width=device-width, height=device-height, initial-scale=1'/>");
				res.write("<h1>사용자 리스트</h1>");

				res.write("<div><ul>");

				for(var i=0; i<result.length; i++) {

					var curId = result[i]._doc.id;
					var curName = result[i]._doc.name;
					var curAge = result[i]._doc.age;

					res.write("<li>#" +  (i+1) + ":" 
							+ curId + ", " 
							+ curName + ", "
							+ curAge + "</li>");
				}

				res.write("</ul></div>");
				res.end();

			} else {

				res.writeHead("200",
						{"Content-Type":"text/html;charset=utf-8"});
				res.write("<meta name='viewport' content='width=device-width, height=device-height, initial-scale=1'/>");
				res.write("<h1>사용자 리스트 조회 실패2</h1>");
				res.end();

			}

		});

	} else {

		res.writeHead("200",
				{"Content-Type":"text/html;charset=utf-8"});
		res.write("<meta name='viewport' content='width=device-width, height=device-height, initial-scale=1'/>");
		res.write("<h1>db연결 실패!</h1>");
		res.end();


	}

};


//사용자를 인증하는 함수
var authUser = function(database,id,pwd,callback) {	

	//id,pw 검색해오기
	database.UserModel.findById(id,function(err,result){ 

		if(err){
			callback(err,null); // 데이터 null
			console.log("찾는 id가 없습니다");
			return;
		} 

		// 조회한 데이터가 있는 경우 콜백함수를 호출하면서 결과를 전달한다
		if(result.length>0){ // 아이디가 일치하면

			console.log("id 일치함");

			// 모델 인스턴스 객체 생성 후 로그인 인증 메소드를 호출
			var user = new database.UserModel({id:id});

			var authenticate = 
				user.authenticate(pwd,
						result[0]._doc.salt,
						result[0]._doc.hashed_pwd); // 이 3개를 호출함

			if(authenticate) {
				console.log("비밀번호가 일치합니다");
				callback(null,result);

			} else {
				//비밀번호 일치 안하는 경우
				console.log("비밀번호가 일치X");
				callback(null,null);

			}				
		} else {
			// 아이디가 틀렸을 때
			console.log("일치하는 사용자를 찾지 못함");
			callback(null,null); // 에러도 안나고 데이터도 없으니깐 null로 처리				
		}		
	});	
}

//사용자 추가 함수
var addUser = function(database,id,pwd,name,callback){

	// UserModel 인스턴스 생성 (3개의 값을 가지고) 
	var user = new database.UserModel({"id":id,"pwd":pwd,"name":name});

	// 입력 - 들어가는 데이터는 json형태니깐 [{}]
	user.save(function(err,result){
		//이 save시킬 때, 가상 스키마 영역으로 가서 암호화 작업이 이뤄지며 저장됨
		if(err){
			throw err;
		}
		console.log("사용자 추가됨");
		
		callback(null,result);
		//callback(에러x, result값반환);

	});
}


module.exports.login = login;
module.exports.addUser= addUsers;
module.exports.listUser = listUser;

 

4. 메인 파일 - app2.js

var express = require("express");
var http = require("http");
var path = require("path");

var bodyParser = require("body-parser");
var serveStatic = require("serve-static"); 
var expressErrorHandler = require("express-error-handler");
var errorHandler = require("errorhandler");
var expressSession = require("express-session");
//var mongoose = require("mongoose"); 필요X - database.js에있음

//user.js 모듈 추가
var user = require("./router/user");

//config.js 모듈 추가
var config = require("./config");
//config.js를 config란 새폴더 생성해서 안으로 넣어놔도됨 ../ 보여주려고 따로 빼놓은것

//database.js 모듈 추가
var database = require("./database/database");

//router_loader.js 모듈 추가
var routerLoader = require("./router/router_loader");

var app = express();
app.set("port",process.env.PORT || config.server_port); // 3000

app.use(bodyParser.urlencoded({extended:false}));
app.use(bodyParser.json());

app.use("/public1",serveStatic(path.join(__dirname,"public1")));  

app.use(expressSession({

	secret:"myKey", // 사용자 정의 세션 이름
	resave:true,
	saveUninitialized:true

}));

//라우팅 함수 등록 : var router = express.Router(); 이렇게 하던거를 아래와 같이
// express.Router() : 라우터객체 바로 넘김
routerLoader.init(app,express.Router()); 

//404 에러 처리
var errorHandler = expressErrorHandler({
	static:{
		"404":"./public/404.html"		
	}
});

//미들웨어 등록
app.use(expressErrorHandler.httpError(404)); //404에러가 났을 때 실행해라
app.use(errorHandler);

// host번호 부여
var host = "192.168.16.14";

//Express 서버 시작
http.createServer(app).listen(app.get("port"),host,function(){
	console.log("서버를 시작했습니다");
	database.init(app,config);
});



이제 localhost로 접속안하고 host번호를 부여한다.

db_url : "mongodb://localhost:27017/shopping"  - DB는 내꺼니까 localhost 안 바꿔도 된다