게시판 - 계정 비밀번호 암호화(bcrypt)

소스코드

이 게시물에는 코드작성이 포함되어 있습니다. 소스코드를 받으신 후 진행해 주세요. MEAN Stack/개발 환경 구축에서 설명된 프로그램들(git, npm, atom editor)이 있어야 아래의 명령어들을 실행할 수 있습니다.

이 게시물의 소스코드는 게시판 만들기 / 게시판 - 회원가입에서 이어집니다.

board.git 을 clone 한 적이 있는 경우: 터미널에서 해당 폴더로 이동 후 아래 명령어들을 붙여넣기합니다. 폴더 내 모든 코드가 이 게시물의 코드로 교체됩니다. 이를 원치 않으시면 이 방법을 선택하지 마세요.

git reset --hard
git pull
git reset --hard f1cd20c
git reset --soft 282faaf
npm install
atom .

board.git 을 clone 한 적이 없는 경우: 터미널에서 코드를 다운 받을 폴더로 이동한 후 아래 명령어들을 붙여넣기하여 board.git 을 clone 합니다.

git clone https://github.com/a-mean-blogger/board.git
cd board
git reset --hard f1cd20c
git reset --soft 282faaf
npm install
atom .

- Github에서 소스코드 보기: https://github.com/a-mean-blogger/board/tree/f1cd20c0fa86c0fa9094a444d4938d074368b307


게시판 - 회원가입 강의에서 user CURD를 만들어 보았습니다. user에는 비밀번호가 있고 이 비밀번호가 DB에 바로 저장되고 있는데, 사실 비밀번호는 반드시 암호화해 주어야 합니다.
이 포스팅에서는 bcrypt-nodejs package를 사용해서 password 대신 password hash를 저장하는 방법을 알아 봅시다.
Hash에 대해 잘 모르시는 분들은 반드시 토막글Hash를 먼저 읽고 진행해 주세요.

bcrypt는 hash를 만드는 알고리듬 중의 하나입니다.

폴더구조

User.js만 변경됩니다.

Package 설치

$ npm install --save bcrypt-nodejs

코드

// models/User.js

var mongoose = require("mongoose");
var bcrypt = require("bcrypt-nodejs"); // 1

// schema ...
// virtuals ...

// password validation
userSchema.path("password").validate(function(v) {
 var user = this;

 // create user ...

 // update user
 if(!user.isNew){
  if(!user.currentPassword){
   user.invalidate("currentPassword", "Current Password is required!");
  }
  if(user.currentPassword && !bcrypt.compareSync(user.currentPassword, user.originalPassword)){ // 2
   user.invalidate("currentPassword", "Current Password is invalid!");
  }
  if(user.newPassword !== user.passwordConfirmation) {
   user.invalidate("passwordConfirmation", "Password Confirmation does not matched!");
  }
 }
});

// hash password // 3
userSchema.pre("save", function (next){
 var user = this;
 if(!user.isModified("password")){ // 3-1
  return next();
 } else {
  user.password = bcrypt.hashSync(user.password); // 3-2
  return next();
 }
});

// model methods // 4
userSchema.methods.authenticate = function (password) {
 var user = this;
 return bcrypt.compareSync(password,user.password);
};

// model & export ...

1. require 함수로 bcrypt-nodejs pacakge를 bcrypt 변수에 담았습니다.

2. bcrypt 의 compareSync 함수를 사용해서 저장된 hash와 입력받은 password의 hash가 일치하는지 확인합니다.
bcrypt.compareSync(user.currentPassword, user.originalPassword)에서 user.currentPassword는 입력받은 text값이고 user.originalPassword는 user의 password hash값입니다. hash를 해독해서 text를 비교하는것이 아니라 text값을 hash로 만들고 그 값이 일치하는 지를 확인하는 과정입니다.

3. Schema.pre 함수는 첫번째 파라미터로 설정된 event가 일어나기 전(pre)에 먼저 callback 함수를 실행시킵니다.
"save" event는 Model.create, model.save 함수 실행시 발생하는 event입니다.
user를 생성하거나 user를 수정한 뒤 save 함수를 실행 할 때 callback 함수가 먼저 호출됩니다.

3-1. isModified함수는 해당 값이 db에 기록된 값과 비교해서 변경된 경우 true를, 그렇지 않은 경우 false를 return하는 함수입니다. user 생성시는 항상 true이며, user 수정시는 password가 변경되는 경우에만 true를 리턴합니다.
user.password의 변경이 없는 경우라면 이미 해당위치에 hash가 저장되어 있으므로 다시 hash를 만들지 않습니다.

3-2. user를 생성하거나 user수정시 user.password의 변경이 있는 경우에는 bcrypt.hashSync 함수로 password를 hash값으로 바꿉니다.

4. user model의 password hash와 입력받은 password text를 비교하는 method를 추가합니다. 이번 예제에 사용되는 method는 아니고 나중에 로그인을 만들때 될 method인데 bcrypt를 사용하므로 지금 추가해봤습니다.

실행결과

코드의 실행결과는 이전과 같습니다. 사용자는 뭐가 달라졌는지 전혀 알 수 없죠.

마치며...

이번 강의는 내용이 짧지만 비밀번호 인증이 어려운 것이 아니고 DB의 data와 입력받은 data를 비교하는 것뿐임을 보여주고 싶었기 때문에 일부러 이전 강의와 분리를 했습니다.

참고로 이번 강의 내용을 코드에 추가하면, 이전강의에서 생성된 user들은 로그인이 안됩니다. 이 점 기억하고 실전에서는 처음에 만들때부터 비밀번호를 hash로 저장해주세요.

댓글

H
Hoon Daniel Kang 2016.12.01
좋은 포스팅 감사합니다. ^^
질문이 하나 있는데요.... 다른 부분은 다 잘 동작하는데 password 변경시
Not a valid BCrypt hash 라는 메세지가 뜨면서 사이트에 연결할 수 없다고 합니다.... 무엇이 문제일까요 ㅠ
I
Ian H 2016.12.02
@Hoon Daniel Kang,
hash가 들어가야 할 부분이 일반 text가 들어가면 그 오류가 나는데요, 로그인은 되는데 변경시에만 일어나는 건 좀 신기하네요. DB을 싹 다 지우고 다시 한번 테스트해보세요.
H
Hoon Daniel Kang 2016.12.02
@Ian H,
DB 지우고 다시 해봤는데도 그러네요....  중간에 DB 처음 사용될 때 Mongoose : mpromise 관련 해서 커맨드 창에 warning 메세지가 뜨긴 하던데 이것 때문일까요? ㅠ.ㅠ
I
Ian H 2016.12.02
@Hoon Daniel Kang,
Mpromise는 그거랑 상관은 없어요. 소스코드를 github에 올려주시면 제가 한번 봐볼게요
h
hckyun 2016.12.06
@Ian H,
저도 Hoon Daniel Kang 처럼 변경시에만 동일한 에러를 만났습니다. 확인해 보니 변경할 때는 user.password에 hash가 아닌 일반 text가 들어가고 있었습니다. 따라서, 아래와 같이 수정했습니다.
userSchema.methods.authenticate = function(currentPassword, originalPassword) {   var user = this;   return bcrypt.compareSync(currentPassword, originalPassword); };
I
Ian H 2016.12.09
@hckyun,
강좌의 코드를 수정했습니다. user schema에 붙이는 method는 user model의 값을 사용해야 의미가 있는데 두개의 값을 입력받아서 두개의 값을 bcrypt.compareSync함수로 비교하면 굳이 해당 함수를 user schema에 붙여야 할 필요가 없죠. userSchema.methods.authenticate는 나중에 login시에 password를 입력받아서 DB값과 비교하려고 만든 건데 잘못 사용했어요 ㅠㅠ.
2번 4번 부분의 설명이 변경되었으니 다시 확인해 주세요. 잘못된 코드가 업로드 된 점 사과드립니다 ㅠㅠ
G
GS 2018.08.31
안녕하세요. 게시글 잘 보고 있습니다. userSchema.pre 메소드에서 조건문으로 !user.isModified("password")를 통해 패스워드를 수정하지 않으면 next()로 넘어가고, 수정하면 이를 hash로 바꿔서 user.password에 저장하는 것으로 되어있는데요. 혹시 그러면 처음에 User를 생성할 당시 패스워드는 따로 hash로 바꿔서 저장해놓는게 없는건가요? DB에 password를 저장하지 않고있는데, 수정한 password는 왜 hash로 바꿔서 저장해놓는지 궁금합니다!
I
Ian H 2018.08.31
@GS,
안녕하세요, !user.isModified("password") 조건문 안에 console.log를 넣고 user를 생성해보면 알 수 있습니다.  user를 생성할 때 해당 조건문의 코드를 실행하는 것을 볼 수 있는데요, password가 없다가 생긴 것을 modified에 해당한다고 보는 게 아닌가 싶습니다.
정현우 2018.09.06
안녕하세요 올리신 이번 암호화 강의에서 궁금한 점이 있어서 문의 드립니다
해당 2번 bcrypt.compareSync 이 함수 자체게 ( 1번 파라미터,2번파라미터) 1번 파라미터는 해시값으로 변경 하는거고 2번째 비교대상의 파라미터가 오는건가요? 파라미터의 순서가 중요한 건가요
I
Ian H 2018.09.06
@정현우,
맞습니다. 첫번째 인자로 hash가 아닌 값을, 두번째 인자로 hash값를 받아서 값을 비교합니다. 공식 문서(https://www.npmjs.com/package/bcrypt-nodejs)에서 항상 저 순서를 따르는 걸 보니 파라미터의 순서가 반드시 맞아야 되는 것 같은데, 순서를 바꿔도 작동하는지 한번 실험해 보시고 저한테도 알려주세요^^
정현우 2018.09.10
변경해서 돌려 보았지만  패스워드가 일치하지 않는다는 json 형태의 에러가 발생합니다 ~  보내주신 api 에서도   compareSync(data, encrypted) data - [필수] - 비교할 데이터입니다. encrypted - [REQUIRED] - 비교할 데이터입니다. 나와 있는걸 확인했습니다 감사합니다 ^^
I
Ian H 2018.09.10
@정현우,
아 공식문서에 나와있네요. 확인 감사합니다^^
칠동이 2019.02.14
올려주신 코드로 실행했을때 Sign up 누르고 정보 입력하고 submit을 누르니 아래와 같은 오류 메세지가 뜹니다
{"driver":true,"name":"MongoError","index":0,"code":11000,"errmsg":"E11000 duplicate key error index: poemiyu.users.$nickname_1 dup key: { : null }"}
또한 콘솔창에는 (node:6996) DeprecationWarning: collection.findAndModify is deprecated. Use findOneAndUpdate, findOneAndReplace or findOneAndDelete instead. 라고 뜨더군요 db에러일까요?
I
Ian H 2019.02.14
@칠동이,
E11000 duplicate key error index: poemiyu.users.$nickname_1 dup key: { : null }
nickname_1이 중복이라고 합니다. DB에서 data를 한번 확인해 보세요
칠동이 2019.02.18
@Ian H,
mongo db 사용법을 아직 잘 몰라서요 데이터 조회 및 삭제 어떻게 하나요?
I
Ian H 2019.02.18
@칠동이,
제 강의를 따라오셨으면 mlab.com에서 데이터를 볼 수 있습니다. 
댓글쓰기

이 글에 댓글을 다시려면 SNS 계정으로 로그인하세요. 자세히 알아보기

UP