무작정 개발.Vlog

[node.js] 암호화(crypto) 모듈, 모듈 분리 방법(1)

by 무작정 개발
반응형
2022.04.06(72일 차)

 

이번에는 암호화 모듈, 모듈 분리 방법에 대해 정리할 것이다.

회원 가입을 할 때 비밀번호를 암호화해서 DB에 저장할 것이고, 1개의 js파일에서 소스를 작성하였는데

Java를 할 때 클래스 단위로 나누는 것처럼 모듈을 분리해서 할 것이다.

 

list 페이지
list 페이지

[기능]

 

회원 가입 시 비밀번호가 암호화돼서 저장되고, 로그인 시 DB에 저장된 비밀번호와 사용자가 입력한

비밀번호를 비교해서 로그인을 한다.

 

 

오늘의 수업 내용

 

암호화 모듈(crypto 모듈)

 

Crypto 모듈이란?

 

평문(암호화되기 이전의 텍스트)을 암호화하는 데에는 다양한 방법의 암호화 기법이 존재한다.

Crypto 모듈은 node.js에서 데이터 암호화 기능을 제공하는 모듈이다.

 

 

ModuleExe 프로젝트 우클릭 -> cmdHire 클릭해서 cmd창을 연다.

npm install crypto --save 

위의 명령어를 입력하면 ModuleExe 프로젝트에 정상적으로 모듈이 설치가 된다.

 

crypto 모듈은 사용자가 pwd를 입력하면 그 pwd에다가 salt라는 키를 붙여서 암호화한다

 

패스워드가 암호화되서 저장된다.
패스워드가 암호화되서 저장된다.

salt : 패스워드를 암호화하기 위한 키값을 의미함

 

 

모듈 분리 방법

 

 [ 모듈을 분리할 때 사용하는 코드 패턴 3가지 방법 ]


 1. 함수 할당 (모듈 불러온 후 괄호 붙여서 실행)
 2. 인스턴스 객체 할당 (모듈 불러온 후 객체의 메서드나 속성 호출) 
 3. 프로토타입 객체를 할당 (모듈 불러온 후 new로 객체 생성 후 실행)

 

 


암호화 모듈 사용 & 모듈 분리 예제

 

app1.js

  • 함수와 라우터가 들어있는 파일이다.
  • 스키마는 분리해서 userSchema.js로 분리하였다.
// 암호화
// 암호화되는 작업을 하기 위해서 모듈이 필요하다 
// npm install crypto --save 암호화 모듈을 설치한다.
// crypto 모듈은 사용자가 pwd를 입력하면 그 pwd에다가 salt라는 키를 붙여서 암호화 한다

//Express 기본 모듈
require("dotenv").config();
var express = require("express");
var http = require("http");
var path = require("path");
var serveStatic = require("serve-static");
var expressErrorHandler = require("express-error-handler");
var expressSession = require("express-session");
var mongoose = require("mongoose");

//crypto(암호화)모듈
var crypto = require("crypto")

var database;
var UserSchema;
var UserModel;

//데이터베이스 연결
function connectDB(){	
	
	//데이터베이스 연결 정보
	var databaseUrl = "mongodb://localhost:27017/shopping";
	
	//연결
	mongoose.connect(databaseUrl);
	
	database = mongoose.connection;
	
	database.on("open",function(){

		// 스키마 모델 객체
		createUserSchema(); // 함수로 호출할것임
		
	});
	
	database.on("error",console.error.bind(console,"몽구스 연결 에러..."));
	
	database.on("disconnected",function(){
		
		console.log("DB연결이 끊겼습니다 5초후 재연결 합니다");
		setInterval(connectDB(),5000);
	});
	
}

//스키마 정의 한것을 함수로 뺼것임 그 사이에 Virtual를 넣으면 함수가 길어지므로 

function createUserSchema(){
	
	// 스키마를 호출하고 몽구스를 쓰게된다.
	UserSchema = require("./database/userSchema").createSchema(mongoose);
	
	
	
	// Model 정의	
	UserModel = mongoose.model("users3",UserSchema);

	console.log("UserModel 정의함.");
	
}

//익스프레스 객체 생성
var app = express();

app.set("port",process.env.PORT||3000);

app.use(express.urlencoded({extended:false})); //post방식일 때 한글 깨짐 방지

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

app.use(expressSession({
	
	secret:"my key",
	resave:true,
	saveUninitialized:true
	
}));

//사용자를 인증하는 함수
var authUser = function(database,id,pwd,callback){
	
	console.log("authUser 함수 호출..");
			
	//아이디와 비밀번호 검색

	
	//id가 function(id,callback) 에서 find({id:id}로 찾아 오게되면서 결과가 callback함수가 에러면 err 잘나오면result 로 들어감
	// 즉 , 콜백함수는 id를 찾았을경우 실행된결과를 받음

	UserModel.findById(id, function(err,result){ 		// 아이디 패스워드 체크했었는데 이젠 id만 주면됨

		// 에러가 있으면 이걸 실행 
		if(err) {
			callback(err,null);
			return;
		}
		
		//데이터가 있을경우
		if(result.length>0){	
			console.log("아이디와 일치하는 사용자 찾음")
			// 무조건 배열로 넘어옴
			
			// ._doc = 도큐먼트 DB 값을 비교
			
			
			// 암호화된 데이터 비교
			var user = new UserModel({id:id});
			
			var authenticated = 
									
				user.authenticate(pwd,result[0]._doc.salt,result[0]._doc.hashed_password); // 위에 메소드 UserSchema.method("authenticate",function(inputPwd,inSalt,hashed_password) 호출
											// pwd , salt키 = db암호화된 비교작업
			
			// authenticated 에는 true 아니면 fasle 가 들어가게됨
			
		
		} if(authenticated){ // 사용자가 입력한 pwd가 일치하나 ?  	
				console.log("비밀번호 일치함")
				callback(null,result); // 에러는 없으니 null ,데이터 있으니 result 는 넣어줌 	
			
		} else {
			console.log("비밀번호가 일치하지 않음");
			callback(null,null); // 에러없고 결과가 없으니 null , null
			
			}
						
		/*} else {
			
			console.log("아이디와 일치하는 데이터가 없습니다.");
			callback(null,null); // 에러없고 결과가 없으니 null , null
		
			
		}*/
		
	});
	
}

//사용자를 추가하는 함수
var addUser = function(database,id,pwd,name,callback){
	
	console.log("addUser 함수 호출..");
	
	var users = new UserModel({"id":id, "pwd":pwd, "name":name});
	
	users.save(function(err,result){
		
		if(err){
			callback(err,null);
			return;
		}
		
		if(result){
			
			console.log("사용자 추가..");
			
		}else{
			console.log("사용자 추가 실패..");
		}
		callback(null,result);			
	});
	
};
//--------------------------------------------------------
// 라우터 객체 생성
var router = express.Router();

// 로그인 라우터
router.route("/process/login").post(function(req,res){
	
	console.log("/process/login 호출..");
	
	var id = req.body.id;
	var pwd = req.body.pwd;
	
	
	
	if(database){
	
		authUser(database, id, pwd, function(err,result){
			
			if(err) {throw err;}
			
			if(result){
				
				var userName = result[0].name;
				
				res.writeHead("200",{"Content-type":"text/html;charset=utf-8"});
				res.write("<h1>로그인 성공</h1>")
				res.write("<div>아이디: " + id + "</div>");
				res.write("<div>이름: " + userName + "</div>");
				res.write("<br/><br/><a href='/public/login.html'>다시 로그인</a>");
				res.end();				
				
			}else{
				
				res.writeHead("200",{"Content-type":"text/html;charset=utf-8"});
				res.write("<h1>로그인 실패</h1>")				
				res.write("<div>아이디 또는 패스워드를 확인하세요</div>");
				res.write("<br/><br/><a href='/public/login.html'>다시 로그인</a>");
				res.end();
			}	
		});
	}else{
		
		res.writeHead("200",{"Content-type":"text/html;charset=utf-8"});
		res.write("<h1>데이터베이스 연결 실패</h1>")				
		res.write("<div>데이터베이스를 연결하지 못했습니다.</div>");		
		res.end();		
	}	
});

//사용자 추가 라우터
router.route("/process/addUser").post(function(req,res){
	
	console.log("/process/addUser 호출..");
	
	var id = req.body.id;
	var pwd = req.body.pwd;
	var name = req.body.name;
	
	if(database){
		
		addUser(database, id, pwd, name, function(err,result){
			
			if(err) {throw err;}
			
			if(result){
				
				res.writeHead("200",{"Content-type":"text/html;charset=utf-8"});
				res.write("<h1>사용자 추가 성공</h1>")	
				res.end();			
				
			}else{		
				res.writeHead("200",{"Content-type":"text/html;charset=utf-8"});
				res.write("<h1>사용자 추가 실패</h1>")	
				res.end();			
			}
		});
		
		
	}else{
		
		res.writeHead("200",{"Content-type":"text/html;charset=utf-8"});
		res.write("<h1>데이터베이스 연결 실패</h1>")				
		res.write("<div>데이터베이스를 연결하지 못했습니다.</div>");		
		res.end();
		
	}
});

// 사용자 리스트 라우터
// 폼버튼을 눌렀을때의 주소임 
router.route("/process/listUser").post(function(req,res){
	
	console.log("/process/listUser 호출됨")
	
	// db에서 데이터 가져옴
	
	if(database) { // 데이터베이스 객체가 있으면 연결해서 데이터 가져옴
		
		/*UserSchema.static("findAll",function(callback){
			return this.find({}, callback);
		})*/
		
		// 함수 호출하면됨 
		UserModel.findAll(function(err,result){  // 모든데이터 가져오는 메소드 static("findAll",  -> find{} 넘겨주는값이 필요없음 function(err,result) 로 처리 
			
			
			if(err) { // 에러가 발생하면 
				
				res.writeHead("200",{"Content-type":"text/html;charset=utf-8"});
				res.write("<h2>사용자 리스트 조회중 에러 발생</h2>")				
				res.end();
				
				return; // 밑에 실행하지 않게 return 꼭 써줌 
			}
			
			if(result){ // 정상적으로 데이터를 가지고 왔을때
				
				// 모양 디자인 만드는 것임 
				res.writeHead("200",{"Content-type":"text/html;charset=utf-8"});
				res.write("<h2>사용자 리스트</h2>")
				res.write("<div><ul>");
				
				
				for(var i=0; i<result.length; i++) {	
					var id = result[i]._doc.id; // 여러개니까 i번째 
					var name = result[i]._doc.name;
					var age = result[i]._doc.age;
				
					// ul안에 들어가있으니 li , number니까 # 하나 붙이고 i+1은 일련번호 , : 구분자 해주고 엔터치고 id + , + name + , + age + <li>
					res.write('<li>#'+ (i+1) + ' : ' 
							+ id + ", " + name + ' , ' + age + '</li>' )
				}
				
				
				res.write("</ul></div>");
				res.write("<br/><br/><a href='/public/listUser.html'>리스트</a>");
				res.end()
				
			} else {
				// 데이터가 없을 경우 
				res.writeHead("200",{"Content-type":"text/html;charset=utf-8"});
				res.write("<h2>사용자 조회 실패</h2>")				
				res.end();
			}
		})  	
	}	
});

//라우터 객체 등록
app.use("/",router);

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

app.use(expressErrorHandler.httpError(404));
app.use(errorHandler);


//Express 서버 시작
http.createServer(app).listen(app.get("port"),function(){
	
	console.log("익스프레스 서버를 시작했습니다: " + app.get("port"));
	
	//DB연결 함수 호출
	connectDB();
	
});

 

 

userSchema.js

  • 스키마를 따로 분리하여 파일을 생성했다.
var crypto = require("crypto");

//app1.js에 있는 스키를 따로 분리해서 userSchema.js 파일로 만들었다.
//- 여기엔 스키마 분리

// 자바에서 따지면 Schema 라는 객체 
var Schema = {};

Schema.createSchema = function(mongoose){
	
	UserSchema = mongoose.Schema({
		id:{type:String,required:true,unique:true}, // 아이디에 들어가는 값은 문자고 required 반드시 필요하고 , 유의값 - > 프라이머리키가 됨
		hashed_password:{type:String,required:true}, // 암호화된 패스워드 이다. 그러므로 hashed_pwd로 바꿈
		// 암호화 + salt , salt키를 저장해야함 안하면 High문자를 저장하면 a123를 넣으면 high라는애를 가지고 와서 붙여서 암호화를 해야하는데 암호화된게 hashed_pwd로 들어가는데
		salt:{type:String,required:true},// 다음에도 123를 붙이는데 앞에 salt키가 있어야되므로 항상 보관되어 있어야 한다. 암호화시킬때 임의로 붙이는 키값이다 . 반드시 필요하므로 required : true
		name:{type:String},
		age:{type:Number,'default':20},
		created:{type:Date,index:{unique:false},'default':Date.now} // unique:false 똑같은값이 들어가도 상관없다 , 날짜를 안넣으면 오늘날짜
	
	});	
	UserSchema
		.virtual("pwd") // 암호는 뭘로 만들건가 password로 만듬 , password값으로 넣겠다
		.set(function(pwd){
			
			this._password = pwd; // 패스워드를 넣을것이다. _password 변수이다. 바꾸면 안됨
			this.salt = this.makeSalt(); // 아래에서 만든 salt키값을 _salt에 넣을것이다.
			
			this.hashed_password = this.encryptPassword(pwd); // 암호화를 시키는 메소드이다 밑에 만들것임
			
	})
	
	.get(function(){
		
		return this._password; // 패스워드 반환값 이 값을 hashed_password:{type:String,required:true} 에 넣는다
	})
	
	
	// 스키마 객체에 메소드를 추가 : method() 방법
	UserSchema.method("makeSalt",function(){ // 메소드를 만들면서 makeSalt를 호출함
		
	//	return "hi"; // hi붙이고싶다 hi라는 문자가 this.makeSalt()에서 호출되는데 salt에 hi가 들어감
					 // db에 salt:{type:String,required:true} 들어감 
					 // hi 123 A , hi 456 B , hi 787 C
					 // 암호화할때 hi만 붙이게되면 패스워드를 암호화하는 방식이 너무 단순
					 // A라는 사람이 로그인할때 hi라고 하지않고 abc123
					 // B라는 사람이 접속하면 123456
					 // c라는 사람이 접속하면 265189
					 // 그 사람만의 고유 salt를 만드는 것이다.
				    // id pw salt
					// A 123 abc = XXX 암호화된 결과물
					// B 456 129
					// C 789 369
			// 즉 독자적인 salt키를 갖게 된다.
			// pw + salt 를  암호화된 pwd와 비교하게 된다 . 
			// 실제패스워드를 저장하지않고 암호화된 패스워드 저장함
			// 각자의 salt키도 저장해야 한다.
		
		// 지금은 시시각각 변하는 난수값을 넣어서 알아내기 쉽지 않다
		console.log("date : " + new Date().valueOf()); // 초에따라 일련숫자 12341242 나옴
		console.log("math : " + Math.random()); // 0.1234214242 매번바뀜
		return Math.round((new Date().valueOf() * Math.random())) + ""; // 랜덤함수를 그순간에 만들어서 뒤에 + 붙여서 문자가됨 그러면 패스워드랑 붙을수있다. String값이됨
		
	}) 		
	
	// 암호화 작업을 하는 곳
	UserSchema.method("encryptPassword",function(inputPwd,inSalt){ // 암호화를 시키려면 입력되는 패스워드 inputPwd 를 넣음
													// 암호화 하려면 salt가 필요하므로 inSalt
													// 그러면 들어가는 패스워드와 salt 가 합쳐져서 암호화가 된다.
		
		// 이미 암호화가 되어있으면 로그인이 되어있으면 123 입력하면
		// 얘를 암호화 해서 기존에 저장되어있는 암호화된 데이터를 같냐 해서 비교작업을 해줘야 하다.
		
		if(inSalt) { // inSalt가 있으면
			// 암호화 작업
			return crypto.createHmac("sha1",inSalt) // sha1 암호화 용어인데 쉬바라고 읽는다. 1은 암호화하는 등급이다. 암호화하는 모듈로써 모든곳에서 다쓴다
				.update(inputPwd).digest("hex"); // hex 헥사라고 읽음 								
		} else {
			// 안주게 되면  this 자체적으로  salt키를가지고 있으면 그걸써라
			return crypto.createHmac("sha1",this.salt) // sha1 암호화 용어인데 쉬바라고 읽는다. 1은 암호화하는 등급이다. 암호화하는 모듈로써 모든곳에서 다쓴다
			.update(inputPwd).digest("hex");
			
		}
		
	}) 
	
	
	// 123를 암호화해서 암호화된 데이터를 비교해야되는 메소드를 만들어야한다.
	// 로그인할때 암호화된 PWD비교
	UserSchema.method("authenticate",function(inputPwd,inSalt,hashed_password){ // 사용자가 입력할 패스워드가 필요 
		// inputPwd + insalt 가 들어오면 123high 해서 암호화한다.
		// 암호화된 데이터가 db에 저장되어있는 암호화된 데이터를 읽어내야 한다
		// 두개를 비교하는데 inputPwd , insalt 와 db에서 꺼내온 암호화된 패스워드 hashed_password가 필요하다
		
		
		if(inSalt) { // 설트키 가 있으면
			
			console.log("사용자 입력 pwd: " + inputPwd); // abc 
			console.log("암호화된 pwd: " + this.encryptPassword(inputPwd,inSalt)); // 암호화된 패스워드를 보여줄것이다. abc가 xxxxxxx
			console.log("DB에 저장되어 있는 pwd: " + hashed_password);// db에 저장되어있는 암호화된 패스워드  abc가 db에저장된 암호화된 데이터
			
			// a 123 + salt 와 db에서 읽어온것과 일치하면 true 반환
			return this.encryptPassword(inputPwd,inSalt)==hashed_password;  // 암호화된 시킨것과 db에 저장된것과 일치하나? true , false
			
			
			
		} else {  // 설트키 가 없으면 패스워드만 감
				  // 위에서 부르면 function 에 inSalt가 없으면 this.Salt 로 감
			
			console.log("사용자 입력 pwd: " + inputPwd); // abc 
			console.log("암호화된 pwd: " + this.encryptPassword(inputPwd,inSalt)); // 암호화된 패스워드를 보여줄것이다. abc가 xxxxxxx
			console.log("DB에 저장되어 있는 pwd: " + hashed_password);// db에 저장되어있는 암호화된 패스워드  abc가 db에저장된 암호화된 데이터
			
			return this.encryptPassword(inputPwd)==this.hashed_password;
		}

		
	}); 
	
	//스키마 객체에 메소드를 추가(방법: static(), method() )
	
	//로그인에서 사용
	// findById는 메소드의 이름 뒤에 콜백함수 
	
	// static를 써서 메소드 
	UserSchema.static("findById",function(id,callback){ // 아이디를 가지고 select 했을때 들어갔을때 무조건 작업이 끝났을때 결과가 콜백으로 간다.
		return this.find({id:id},callback); // id를 찾아라 찾던안찾던 callback함수 실행해라
	})
	
	
	
	// 전체 데이터 사용
	UserSchema.static("findAll",function(callback){ // findAll 메소드를 
		return this.find({}, callback); // 전체데이터는 조건이 없어서 {} 몽땅가져옴 
	})
	
	console.log("UserSchema 정의함.");
		
		return UserSchema;
	}
	
	
module.exports = Schema; // 외부에서 이 파일을 엑세스 할수있다.

 

 

반응형

블로그의 정보

무작정 개발

무작정 개발

활동하기