게시판 시작해봅시다 -5
지난 포스팅에서 만든 유저는 password 문자열이 그대로 DB에 저장되기 때문에 해커에 의해 DB가 해킹당할 경우 password가 그대로 해커의 손에 들어가게 됩니다.
사이트의 보안성을 높히기 위해 password를 암호화하게 되는데, 저는 bcrypt라는 방법으로 password를 암호화 하겠습니다.
bcrypt는 문자열을 hash로 바꾸어 주는데, DB에는 실제 password 대신 이 hash가 저장되게 됩니다. 만약 해커가 이 hash값을 취득하게 되더라도 실제 password를 알 수가 없습니다. 왜냐하면 문자열을 hash로 만들 수는 있지만, hash는 문자열로 되돌릴 수 없게 되어 있기때문입니다. 그럼 어떻게 이 hash를 사용하냐면, hash를 문자열로 되돌릴 순 없지만, 입력받은 문자열을 hash로 만들었을때 이 값이 기존의 hash와 일치하는지는 알 수 있기때문에 흔히들 'hash 값을 비교(compare)한다'라고 합니다.
bcrypt 사용을 위해 bcrypt-nodejs라는 package를 깔아줍니다.
$ npm i bcrypt-nodejs --save |
package.json 모습입니다.
왜인지는 모르겠는데, 지난번 commit에 cookie-parser가 들어가 있는 것을 발견했습니다. 24번째 줄에 있죠. 하지만 현재 강의에서 사용하지 않는 package이므로 무시하셔도 됩니다. 물론 그냥 package.json에 그냥 두셔도 아무 영향 없습니다.
이제 본격적으로 코드를 수정합시다.
32번째 줄 : bcrypt는 userSchema바로 위에 require하도록 하겠습니다. 왜 몇몇 package는 제일 위에 require하지 않고 중간에 require하냐면.. 나중에 refactoring할때를 위해서 입니다. 일단은 그냥 그런가 보다 하고 넘어가시면 됩니다.
39~47번째 줄 : User 모델이 "save"되기 전(pre)에 모델에 대해서 할 일을 schema에 저장하는 단계입니다. "save"된다는 말은 user.save()함수를 호출한다는 말인데, 이 강좌에서 이 함수를 직접 호출한 적은 없습니다. User.create함수를 쓰게 되면 마지막에 자동으로 user.save()함수가 호출됩니다. 이때 DB에 저장을 하기 전에 할 행동을 지정하는 것입니다. 참고로 직접 user.save()를 호출해서 쓸 수도 있는데(오늘 강좌에서 한번 사용됩니다.) 예를 들어, User.find({email:"test"}, function(err,user){ ... }) 이런 식으로 user를 찾은 다음, user 개체에 임의로 속성을 더하거나(ex> user.name = "test name"), 기존의 속성을 수정한 후 user.save()해버리면 내용이 수정됩니다.
40번째 줄 : 가독성을 위해 this 키워드를 user에 저장했습니다. this 키워드는 다들 알고계시리라 믿습니다. 모르시면 검색해 보시기 바랍니다.
41~42번째 줄 : user.isModified() 역시 mongoose에서 지원하는 함수로, ()안의 속성이 db상의 값과 차이가 있는지를 보는 함수로, 현재는 "user.password"의 변화가 없다면 더 이상 진행하지 않고 next로 넘어갑니다.
44번째 줄 : user.password의 변화가 있다면(새로 생성시에도 해당됩니다.) bcrypt로 생성한 hash값으로 교체합니다. bcrypt.hashSync에서 Sync는 동기(sync)식 함수라는 뜻입니다. 그냥 bcrypt.hash함수를 사용하면 비동기(async)로 hash를 생성합니다. 이제 동기식 함수와 비동기식 함수에 대해 감이 잡히시나요?
48~51번째 줄 : 스키마에 authenticate이라는 함수를 더해줍니다. user.authenticate()으로 호출할 수 있습니다.
49번째 줄 : 40번째 줄과 마찬가지입니다.
50번째 줄 : bcrypt.compareSync함수를 사용해서 입력된 password와 db의 hash(user.password)를 비교합니다. Sync가 들어가 있으므로 동기식 함수라는 것을 알 수 있습니다. 이 함수는 hash가 일치하면 true, 일치하지 않으면 false를 return합니다.
이전 코드에서는 password확인을 password == user.password로 하였습니다. 이제 db에 hash가 저장되므로 그렇게 사용할 수 없고 user.authenticate()를 사용해야 합니다.
93, 171번째 줄 : user 개체에 있는 hash와 문자열을 비교합니다.
173~174번째 줄 : user 개체에 직접 newPassword를 입력해 주고, user.save()를 하고 있습니다. 178줄의 findByIdAndupdate는 user.save()를 사용하지 않기 때문에 이렇게 사용했습니다.
---------------
위 코드에는 버그가 있습니다!
user password를 update하고 로그인하면 에러가 발생하는데,
http://blog.naver.com/azure0777/220674966025
에서 수정되었습니다.
---------------
167번째 줄의 isLoggedIn과 168 번째 줄은 밑에서 설명하겠습니다.
isLoggedIn은 req.isAuthenticated()를 사용해서 현재 로그인이 되어 있는 상태인지 아닌지를 알려주는 함수로, passport에 의해 제공됩니다. 로그인이 되어 있으면 다음 함수로 진행하고, 안되어 있으면 시작화면으로 보냅니다.
이렇게 계정생성과 로그인, 페이지 접근을 차단하는 방법을 알려드렸습니다. 개인적으로 웹페이지 제작에서 굉장히 까다로운 부분이라고 생각되는데 수고하셨습니다!!
아래는 input type="password"를 사용하여 view에서 password 문자열이 화면에 나타나지 않게 하는 방법입니다.
간단하죠?
그리고 profile에서 password를 보여주는 부분은 빼버렸습니다.
현재 게시판은 로그인과는 별개로 게시판 기능 자체에는 아무 변화가 없습니다. 다음 포스팅에서 작성자를 표시하고, 자신의 게시글을 수정, 삭제할 수 있도록 하겠습니다. 물론 남의 게시글은 삭제할 수 없게 합니다.
github.com/imtaekh/my_app/tree/3cea235b2713f6a0eed0a8fd199da283ab8af00d에서 코드를 보실 수 있습니다. git clone한 다음 git reset --hard 3cea235하시면 컴퓨터에서 코드를 보실 수 있습니다. |