[Node JS] 로그인 회원가입 로그아웃 구현 #2 / 회원가입 / hash / schema / bcrypt

Sign-Up / In / Out implementation

bcryptmongoose를 사용해 비밀번호를 암호화 하고 MongoDB에 유저 정보 저장.

pwc는 비밀번호를 확인해야해서 일부러 아무처리 안하고 저장했습니다.

▼회원가입을 위한 server.js 전체 코드▼

코드복사는 아래 코드로 해주세요! 나중에 설명할때 나오는 코드는 모듈이 포함되어있지 않습니다!
만약 에러가 난다면 해당 모듈을 npm명령어를 통해 설치해주시고 import해주세요

const express = require('express');
const app = express();
app.use(express.static(__dirname + ''));

// allows you to ejs view engine.
app.set('view engine', 'ejs');

// importing .env file
require('dotenv').config();

// importing body-parser to create bodyParser object
const bodyParser = require('body-parser');
// allows you to use req.body var when you use http post method.
app.use(bodyParser.urlencoded({ extended: true }));

// importing db function that connects with MongoDB.
const { db } = require('./module/db');

// importing user schema.
const User = require('./module/user');

// importing bcrypt moudle to encrypt user password.
const bcrypt = require('bcrypt');
// declaring saltRounds to decide cost factor of salt function.
const saltRounds = 10;

db();

app.get('/login', auth, function(req, res) {
    const user = req.decoded;
    if(user) {
        return res.render('loggedin', {user:user.docs});
    } else {
        return res.sendFile(__dirname + '/login.html');
    }
});

app.post('/login/:signUpid/:signUpaddress/:signUppw/:signUppwc', function(req, res, next) {
    let user = new User(req.body);
    if(user.pw!==user.pwc) {
        return res.send('Your password and password confirmation have to be same.');
    }
    User.findOne({id:(user.id)}, function(err, docs) {
        if(err) throw err;
        else if(docs == null) { // Entered ID is available.
            if(user.id&&user.pw&&user.pwc) {    // adding a new account.
                return next();
            } else return res.send('Please enter all the blanks.');
        }
        else {
            return res.send('Your entered ID already exists.');
        }
    });
});

app.post('/login/:signUpid/:signUpaddress/:signUppw/:signUppwc', function(req, res) {
    let user = new User(req.body);
    bcrypt.genSalt(saltRounds, function (err, salt) {
        if (err) throw err;
        bcrypt.hash(user.pw, salt, function (err, hash) {
            if (err) throw err;
            user.pw = hash;
            user.save();
            return res.send('You have just created your new account!');
        })
    })
});

2. 회원가입 구현

login.html파일을 만든 후 위의 폼을 만들기 위해 아래의 코드를 작성했다.
submit버튼을 누르면 SignUpAjax()함수가 그리고 Log in버튼을 누르면 signInAjax()함수가 실행되며 해당 미들웨어로 데이터가 전송된다.

<form id="signup">
	<h1>Sign up example</h1>
	<div class="field">
		<label for="id">ID:</label>
		<input type="text" id="id" name="id" placeholder="Enter your fullname" /><br><br>
	</div>
	<div class="field">
		<label for="pw">Address:</label>
		<input type="text" id="address" name="address" placeholder="Enter your address" /><br><br>
	</div>
	<div class="field">
		<label for="pw">PW:</label>
		<input type="text" id="pw" name="pw" placeholder="Enter your password" /><br><br>
	</div>
	<div class="field">
		<label for="pwc">PW confirmation:</label>
		<input type="text" id="pwc" name="pwc" placeholder="PW confirmation" /><br><br>
	</div>
	<button type="button" onclick="SignUpAjax()">Submit</button><br><br>
</form>
<form id="singin">
	<h1>Sign in example</h1>
	<div class="field">
		<label for="signinID">ID:</label>
		<input type="text" id="signinID" name="signinID" placeholder="Enter your fullname" /><br><br>
	</div>
	<div class="field">
		<label for="signinPW">PW:</label>
		<input type="text" id="signinPW" name="signinPW" placeholder="Enter your password" /><br><br>
	</div>
	<button type="button" onclick="signInAjax()">Log in</button><br><br>
</form>

signUpAjax()함수의 내용은 다음과 같다.
함수가 실행되면 폼의 데이터값을 ajax함수를 통해 미들웨어로 보낸다.

<script>
	function SignUpAjax() {
		const id = document.getElementById("id").value;
		const address = document.getElementById("address").value;
		const pw = document.getElementById("pw").value;
		const pwc = document.getElementById("pwc").value;
		document.getElementById("id").value = "";
		document.getElementById("address").value = "";
		document.getElementById("pw").value = "";
		document.getElementById("pwc").value = "";
		$.ajax({
			type: "post",
			url: 'http://localhost:8080/login/:signUpid/:signUpaddress/:signUppw/:signUppwc',
			data: {id:id,address:address,pw:pw,pwc:pwc},
			dataType:'text',
			success: function(res) {
				window.alert(res);
			}
		});
	}
</script>

하지만 현재 이 웹사이트의 경우 경로는 /login인데 로그인을 한 경우와 하지 않은 경우 둘로 나누면 로그인을 하지 않았을 경우 위의 만들어둔 폼을 보여주고 로그인을 한 경우는 유저가 로그인을 했음을 보여줘야한다. 그러므로 server.js에서 아래처럼 해주었다.

app.get('/login', auth, function(req, res) {
    const user = req.decoded;
  	// user가 있으면 -> 로그인 한 경우 -> 유저정보를 함께 웹페이지와 보내준다.
    if(user) {
        return res.render('loggedin', {user:user.docs});
    } else { // user가 없으면 -> 로그인 필요한 경우 -> 로그인 폼을 보내준다.
        return res.sendFile(__dirname + '/login.html');
    }
});

위의 auth함수와 const user = req.decoded는 로그인 하는 부분에 나올 부분이므로 생략하겠습니다.

Schema

미들웨어로 호출되면 schema를 사용해 user변수에 클라이언트 측으로부터 받은 데이터를 넣어준다. 아래와같이 User.js파일을 module폴더 안에다 만들었다. 나중에 server.js파일에서 사용할 수 있다. 객체의 변수 하나하나에 다르게 옵션을 설정할 수 있다.

// User.js
const mongoose = require('mongoose'); // declaring mongoose.
const userSchema = mongoose.Schema({  // making a schema called userSchema.
  id: { 
    type: String,
    maxLength: 50,
    required: true,
    unique: 1, // exists one unique value
  },
  address: {
    type: String,
    required: true,
    maxLength: 100,
  },
  pw: {
    type: String,
    required: true,
    maxLength: 100,
  },
  pwc: {
    type: String,
    required: true,
    maxLength: 100,
  },
});

const User = mongoose.model('User', userSchema);
module.exports = User; // exporting user schema.

아래와 같이 schema형식의 user.js파일을 server.js파일에서 임포팅 해준 후 ajax통신으로 전달받은 데이터값들을 user변수에 넣어준다.

나머지 부분은 주석에 설명하겠습니다.
코드는 맨 위의 전체코드에서 복붙하고 설명을 봐주세요 모듈과 함수들등은 아래에 따로 적지 않았습니다.

app.post('/login/:signUpid/:signUpaddress/:signUppw/:signUppwc', function(req, res, next) {
    let user = new User(req.body);
// `pw`와 `pwc` 값을 비교후 다르다면 해당 문구를 클라이언트측으로 보내고 미들웨어를 종료한다.
    if(user.pw!==user.pwc) {
        return res.send('Your password and password confirmation have to be same.');
    }
// `MongoDB`의 `findOne`메소드를 이용해 유저의 `id`를 기준으로 검색하고 `docs`변수에 리턴한다. 
// `User.findOne`에서 앞의 `User`부분은 위에서 임포팅한 `schema`의 `User`이다.
    User.findOne({id:(user.id)}, function(err, docs) {
        if(err) throw err; // 에러 던지기
      	// `docs`값이 없다면 -> 중복되는 아이디가 없어서 사용 가능하다면!
        else if(docs == null) { // Entered ID is available.
          	// 아이디, 비밀번호, 비밀번호확인이 모두 있는지 확인(빈값 확인)
            if(user.id&&user.pw&&user.pwc) {    // Enter adding a new account step.
              	// 미들웨어의 `next()`메소드.
                // 미들웨어에서 next인자를 세번째에 선언해야 사용가능.
              	// 같은 POST타입의 같은 경로의 `바로 다음` 미들웨어로 이동한다.
                return next();
            } // 아이디, 비번, 또는 비번확인중 값이 비어있을때
			else return res.send('Please enter all the blanks.');
        }
        else {// 리턴받은 docs의 값이 이미 존재해서 중복된 아이디일 경우.
            return res.send('Your entered ID already exists.');
        }
    });
});

bcrypt & salt

아래는 위 미들웨어의 next()메소드로부터 호출받은 다음 미들웨어다. 위에서 이미 아이디를 만들 수 있는지 거쳤기때문에 아래 미들웨어가 실행되면 바로 만들면 된다.
‘bcrypt’모듈을 이용해 ‘salt’를 생성 후 비밀번호와 함께 해쉬함수에 인자로 넘겨줍니다. Salt를 생성하는 이유는 단순 해쉬만 이용하면 해쉬함수는 단방향의 특성을 가지기때문에, 특정 입력값은 항상 같은 결과값을 가지므로 보안에 취약합니다. Salt라는 아주 작은 임의의 변수를 추가해 전혀 다른 해쉬출력값을 받도록 한 방법입니다. saltRounds변수는 salt를 얼마나 복잡하게 만들었는지를 나타내는 인자로서 숫자가 높을수록 시간이 더 오래걸린며 1이 올라갈수록 시간은 두배 오래걸린다.

const bcrypt = require('bcrypt');
const saltRounds = 10;
app.post('/login/:signUpid/:signUpaddress/:signUppw/:signUppwc', function(req, res) {
  	// user의 정보 다시 넣기
    let user = new User(req.body);
    // salt 생성
    bcrypt.genSalt(saltRounds, function (err, salt) {
        if (err) throw err;
      	// 생성한 salt값을 hash함수에 비밀번호와 넣는다.
        // 비밀번호는 해쉬화 되어서 hash변수로 리턴되어진다.
        bcrypt.hash(user.pw, salt, function (err, hash) {
            if (err) throw err;
            // user.pw 에 리턴된 hash 넣기.
            user.pw = hash;
            // MongoDB에 저장.
            user.save();
            return res.send('You have just created your new account!');
        })
    })
});

References:

  1. https://mongoosejs.com/docs/queries.html
  2. https://expressjs.com/en/guide/writing-middleware.html
  3. https://stackoverflow.com/questions/46693430/what-are-salt-rounds-and-how-are-salts-stored-in-bcrypt

login.html파일에서 ajax함수의 success부분에서 결과를 받은 후 이런식으로 알림이 가도록 할 수 있다.

success: function(res) {
	window.alert(res);
}

MongoDB에 저장된 모습.

좋은 웹페이지 즐겨찾기