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

소스코드

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

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

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

git reset --hard
git pull
git reset --hard 259189d
git reset --soft 7ebeabf
npm install
atom .

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

git clone https://github.com/a-mean-blogger/board.git
cd board
git reset --hard 259189d
git reset --soft 7ebeabf
npm install
atom .

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


이전 강의에서 user CURD를 사용해 회원가입 기능을 만들어 보았습니다. 여기서 user의 비밀번호가 그대로 DB에 저장되는데 비밀번호는 반드시 hash처리가 되어야 합니다.

** Hash에 대해 잘 모르시는 분들은 반드시 토막글Hash를 먼저 읽고 계속 진행해 주세요.

이 포스팅에서는 bcryptjs package를 사용해서 DB에 password 를 hash로 변환하여 저장하는 방법을 알아봅시다.

이번 강의를 통해서 password가 hash되면 이전에 생성된 user들은 더이상 사용이 불가능합니다. 이번 강의를 시작하기 전에 이전강의의 테스트를 위해 생성된 user들은 모두 삭제해 주세요.

폴더구조

Package 설치

$ npm install --save bcryptjs

코드

// models/User.js

var mongoose = require('mongoose');
var bcrypt = require('bcryptjs'); // 1

...

  // update user
  if(!user.isNew){
    if(!user.currentPassword){
      user.invalidate('currentPassword', 'Current Password is required!');
    }
    else if(!bcrypt.compareSync(user.currentPassword, user.originalPassword)){ // 2
      user.invalidate('currentPassword', 'Current Password is invalid!');
    }

    ...

// 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);
};

...

1. require함수로 bcryptjs 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를 반환하는 함수입니다. 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의 password와 입력받은 password를 비교하는 것뿐임을 보여주고 싶었기 때문에 일부러 분리를 했습니다.

댓글

류다솔 2020.02.20
안녕하세요~ 선생님 글 덕분에 즐겁게 노드 배우는 취준생입니다~
bcrypt-nodejs를 npm으로 인스톨 할 때 deprecated가 떠서,  bcrypt-nodejs의 차기 버전인 것 같은(?)  bcrypt를 사용했더니 db로 데이터가 전달되지 않고,  submit이후 bash에서 error 메시지도 보이지 않습니다. 크롬에서는 '{}' 단 하나만 출력되서 보이구요
1. 분명 같은 package인거 같은데 실행이 안되는 이유가 있을까요? 2. deprecate 된 패키지를 계속 사용하는 것에 문제는 없을까요?
좋은 컨텐츠 제공해주셔서 감사합니다~
류다솔 2020.02.20
아 bcryptjs를 사용하니 잘됩니다~ :D
I
Ian H 2020.02.20
@류다솔,
안녕하세요! bcrypt에 대해 제보주셔서 감사합니다! 강의는 bcryptjs 패키지로 수정하였습니다.
bcrypt-nodejs, bcryptjs, bcrypt 는 모두 다른 사람에 의해 만들어진 다른 패키지입니다. 누구나 node package를 만들어서 배포할 수 있어요. 현재 프로그래밍은 이처럼 집단지성으로 발전해나가고 있죠.
bcrypt-nodejs의 경우 개발자가 공식적으로 '난 더이상 이 패키지를 업데이트하지 않겠다'고 선언한 것입니다. 이 경우에는 만약 node가 업데이트되서 서로 호환이 안되는 경우 더이상 작동하지 않게 됩니다. bcrypt나 bcryptjs는 개발자가 코드를 수정하여 잘 작동하게 하겠지요.
다시한번 강의에 도움이 되는 댓글을 남겨주셔서 진심으로 감사합니다^^
한성민 2020.06.04
안녕하세요!! node 교육 잘 보고있습니다 ㅎㅎ
질문이있어서 댓글드립니다.
제가 만들고있는페이지에 교육해주신 기능들을 넣으려고하는데 board.git 은 단순히 작성하신 코드들의 집합인건가요??
board.git을 다운받지않고 교육해주시는 코드들을 제 코드에 추가시켜도 아무문제는없나요?? 
항상 좋은강의 감사드립니다
I
Ian H 2020.06.04
@한성민,
board 프로젝트 폴더 안에 .git이라는 폴더가 있는데요, 이 코드는 version control을 위한 데이터가 들어 있습니다. 자세한 것은 https://www.a-mean-blog.com/ko/blog/MEAN-Stack/개발-환경-구축/Git과-GitHub 에서 읽어주세요.
결론적으로 그냥 코드만 가져다 쓰셔도 문제없습니다^^
인간 2020.08.06
좋은 강의 감사합니다! 3-1 에서 "isModified함수는 해당 값이 db에 기록된 값과 비교해서 변경된 경우 true를, 그렇지 않은 경우 false를 반환" 한다고 하셨는데, 그러면 isModified함수는 호출시 자동으로 password를 해시값으로 바꾼후 db의 해시값과 비교하는것인지요?
I
Ian H 2020.08.06
@인간,
아닙니다. isModified함수는 DB로부터 모델이 생성된 후 해당 값이 변경된적이 있는지를 알려줍니다.
isModified('password')를 통해 user의 password의 값이 변경된적이 있는지를 알 수 있는데요, 물론 DB상에서는 hash 값이 password에 저장되어 있고, user edit을 통해 새로운 값을 입력받는 경우 입력받은 값이 그대로 있습니다.
그렇기 때문에 isModified('password')이 true인 경우 user.password = bcrypt.hashSync(user.password); 를 사용해서 해당 값을 hash로 변경해 주는 것입니다.
인간 2020.08.10
@Ian H,
죄송합니다 제 질문이 조금 모호했군요^^; 1. 처음 회원가입시 password를 "1234asdf" 로 입력하면 2. isModified('password') 는 true 이므로, bcrypt.hashSync 가 실행되어 서버에 해시값h1로 변환되어 저장되고 3. 이 상태에서 edit 를 통해 new password를 "1234asdf" 로 재입력하면  4. isModified('password') 는 ("1234asdf" != 서버의 해시값h1) : true 가 되어서 password가 서버에 해시값h2 로 변환되어 저장되거나, 4-1. isModified('password') 는 ("1234asdf" != "1234asdf") : false 가 되어서 바로 next() 가 실행되거나, 4-2. isModified('password') 는 (서버의 해시값h1 != 다시 생성된 해시값h2) : false 가 되어서 바로 next() 가 실행되는 경우 중 하나일 것 같은데, 어떤 경우라고 봐야하나요?
I
Ian H 2020.08.10
@인간,
4번으로 처리됩니다. DB에는 항상 해시값이 저장되고, DB에서 읽어온 모델에도 해시값이 저장되어 있습니다. 새로 입력받으면 일반 값이 들어 있으므로 isModified('password')는 true가 됩니다.
제대로 답변이 되었나요? 안되었다면 부담갖지 마시고 더 질문해주세요^^
인간 2020.08.10
@Ian H,
그렇게 처리되는군요! 빠른 답변 감사합니다^^
I
Ian H 2020.08.11
@인간,
보통 주중 하루에 한번 댓글달러 오는데 시간대가 잘 맞았어요^^;
강민규1 2020.09.03
안녕하세요 덕분에 노드를 잘 공부하고 있는 사람입니다.
강의를 보면서 항상 감사한 마음을 가지고 있습니다 ㅎㅎ
다름이 아니라
userSchema.pre('save', function(next) { 실행을 하고 console.log(user); 를 출력해 보면 user: {"_id":"5f506ce4dbf2cee258ad3588","username":"2","name":"2","email":"2","password":"2","createdAt":"2020-09-03T04:11:16.990Z"} 이와 같은 값이 찍히면서 제대로 작동을 하는데요
 userSchema.pre('save', (next) => { 이렇게 화살표 함수로 변경하고 똑같이 console.log(user); 를 출력해 보면 user: {"_passwordConfirmation":"2"} 이 값만 나오면서 정상작동을 안하는데
왜 이런 차이가 생기는건지 질문드려 봅니다
I
Ian H 2020.09.03
@강민규1,
안녕하세요^^ 반갑습니다. function 키워드를 사용했을 때와 화살표 함수를 사용했을 때 this가 가리키는 값이 달라지기 때문입니다.
즉, 모든 함수를 화살표 함수로 바꿀 수 있는 것이 아닙니다. this가 있다면 화살표함수로 바꿀 수 없을 수도 있습니다. 위 경우에는 화살표 함수로 바꾸면 원래 this에 접근할 방법이 사라지므로 바꾸지 말아야 합니다.
자세한 내용은 https://www.a-mean-blog.com/ko/blog/토막글/_/Javascript-화살표-함수-Arrow-Functions 에서 확인해주세요.
강민규1 2020.09.04
@Ian H,
빠르고 친절한 답변 감사합니다 (__)
전 단순히 구버전 문법 = function(){}, 신버전 문법 () => {} 로 이해하고 있었어서 왜 안되는지 이해를 못하고 있었는데
단순 문법 차이가 아니였었네요 ^^ 덕분에 하나 더 배웠습니다. 감사합니다 ^^)/
I
Ian H 2020.09.04
@강민규1,
도움이 되서 다행이네요^^ 또 궁금하신 점 있으시면 질문해주세요(__)
댓글쓰기

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

UP