유튜브 클론코딩 복습노트-8

Hyuno Choi·2021년 8월 9일
0
post-thumbnail

2021년 8월 9일

지난 시간까지 비디오 데이터를 DB에서 다뤄보았습니다. 이번 포스팅부터는 유저 인증 및 회원가입과 관련된 부분을 만들어보겠습니다.

유저 모델 생성

회원가입 폼을 통해 받으려는 유저 정보는 다음과 같습니다.

  • 이메일 주소
  • 아이디(username)
  • 비밀번호
  • 닉네임(name)
  • 위치

이제 이 정보들을 저장할 유저 스키마를 구현하겠습니다. 우선, 모든 속성의 타입은 String 입니다. 또한 모든 정보를 빠짐없이 받을 것이기 때문에 모든 속성의 required 옵션도 모두 true로 설정해줄 것입니다. 또한 이메일과 아이디는 다른 유저들끼리 겹쳐서는 안 됩니다. 이메일과 아이디 속성에는 unique 옵션에 true를 주겠습니다.

위의 내용을 바탕으로 유저 스키마는 이렇게 구현할 수 있을 것입니다. 만들어진 스키마를 가지고 유저 모델을 만들어 User에 저장합니다. 그리고 다른 파일에서 사용할 수 있도록 export default 로 유저 모델을 내보냅니다.

데이터베이스 및 서버의 초기화를 담당하는 파일은 init.js입니다. 여기서 유저 모델을 불러옵니다.

회원가입 페이지 뼈대 만들기

이제 회원가입 페이지를 사용할 수 있도록 뼈대를 만들어주겠습니다. 회원가입 페이지를 만들려면 다음과 같은 과정을 거쳐야 합니다.

  • 네비게이션에 회원가입 링크 만들기
  • 회원가입 페이지 pug 파일 만들기
  • globalRouter/join 경로의 GET, POST 요청 받을 수 있게 하기
  • GET 요청에 응답해 페이지 렌더하는 컨트롤러 구현하기
  • POST 요청에 응답해 회원가입 정보를 DB에 저장하는 컨트롤러 구현하기

위에서부터 차근차근 만들어보겠습니다.

회원가입 링크 생성

가장 먼저, 사용자가 /join 요청을 보낼 수 있도록 회원가입 링크를 네비게이션에 만들어주겠습니다. 네비게이션은 base.pug 파일에서 생성했기 때문에 해당 파일에서 수정합니다.

이렇게 해서 네비게이션 리스트 안에 총 네 개의 링크가 생겼습니다.

join.pug 파일 생성

프로젝트 폴더 내의 /src/views 디렉토리 안에 join.pug 파일을 생성합니다. 이 파일은 /join 요청이 들어왔을 때 렌더됩니다. extends base 를 사용해 템플릿을 불러온 후 필요한 유저 데이터들을 받을 수 있는 폼을 만들어줍니다. 참고로 받아야 했던 유저 데이터는 다음과 같습니다.

  • 이름
  • 이메일
  • 닉네임
  • 비밀번호
  • 위치

개인정보를 전송하므로 폼의 method 속성은 POST로 설정합니다. 또한 각각의 input 태그는 사용자가 입력을 빼먹지 않게 required 속성을 줍니다.

globalRouter 수정

globalRouter 에는 프로젝트 초반에 이미 /join 경로의 GET 요청을 받을 수 있게 해놓았습니다. 이제 POST 요청까지 받을 수 있도록 수정해주겠습니다.

globalRouter.route("/join") 을 사용해서 두 요청을 한 줄의 코드로 받을 수 있습니다. Express 공식 문서에서는 이렇게 함으로써 코드 반복을 줄이고 타이핑 실수를 방지할 수 있다고 합니다.

그리고 GET과 POST 요청에 대응되는 getJoinpostJoin 컨트롤러를 달아줍니다. 물론 컨트롤러는 별도의 파일에 만들 것이므로 import 해서 사용해야 합니다.

globalRouter => rootRouter

지금 보니 videoRouter, userRouter 에 비해 globalRouter 는 라우터의 이름이 직관적이지 않은 것 같습니다. 글로벌이란 이름만 보고서는 이 라우터가 무슨 일을 하는지 설명해주기 전엔 헷갈립니다. userRouter/users 경로를 담당하고 videoRouter/videos 경로를 담당하듯이 globalRouter 는 루트 경로를 담당하고 있으므로 rootRouter 로 이름을 바꿔주도록 하겠습니다.

우선 globalRouter.js 파일의 이름을 rootRouter.js 로 바꿉니다. 그리고 파일 안의 globalRouter 도 전부 변경해줍니다.

rootRouterserver.js 파일에서 불러와 사용하고 있으므로 server.js 파일 안의 이름도 전부 바꿔줍니다.

getJoin 컨트롤러 구현

/join의 GET 요청과 POST 요청을 처리해줄 수 있는 컨트롤러를 videoController.js 파일에 만들어주겠습니다. 우선 간단한 getJoin 컨트롤러를 만듭시다. 이 컨트롤러의 역할은 화면에 join.pug 파일을 렌더해주는 것입니다.

res.render() 의 인자로는 퍼그 파일에서 사용하는 자바스크립트 변수인 pageTitle 을 객체 형태로 전달합니다. 이제 네비게이션에서 Join 링크를 누르면 join.pug 파일이 렌더됩니다.

getPOST 컨트롤러 구현

이제 회원가입 데이터가 POST 방식으로 전송되면 데이터를 받아와 DB에 저장하는 기능을 구현하겠습니다. 폼에서 데이터를 POST 방식으로 제출하면 데이터가 HTTP 바디 안에 들어가게 됩니다. 따라서 모든 데이터는 req.body 에서 가져옵니다.

DB저장은 비디오 저장 때와 마찬가지로 mongoose의 Model.create() API를 사용하겠습니다. 인자로 유저 데이터를 객체 형태로 전달합니다. User 모델을 사용하는 것이므로 import User from "../models/User"; 를 사용해 불러와야 합니다. 또한 외부 데이터베이스에 저장하는 것이므로 비동기 처리를 해주어야 합니다.

마지막으로 회원가입 폼을 제출하면 회원가입 페이지에 남아있을 필요가 없기 때문에 사용자를 로그인 페이지로 리디렉트 시킵니다.

완성된 getPOST 컨트롤러 입니다. 이제 실제로 /join 페이지로 들어가 회원가입 정보가 제대로 전달되는지 테스트해보겠습니다. 회원가입 폼을 작성하고 제출 버튼을 누르면 정상적으로 홈 화면으로 이동합니다.

아직 로그인 기능은 없으므로 몽고DB에 직접 접속해서 유저 데이터를 확인하겠습니다. 터미널에서 다음 명령어들을 순서대로 입력합니다.

  1. $ mongo
  2. > use wetube (여기부터 몽고 쉘 내부)
  3. > db.users.find({}).pretty()

이렇게 유저 객체가 잘 저장된 것을 볼 수 있습니다.

{
	"_id" : ObjectId("611636b4d66cf0fd4153dbc3"),
	"name" : "Hyuno Choi",
	"email" : "soonitoon@gmail.com",
	"username" : "soonitoon",
	"password" : "zxcvbn",
	"location" : "Korea, Republic of",
	"__v" : 0
}

비밀번호 해싱

위의 유저 정보처럼 데이터를 저장하면 보안에 굉장히 취약해집니다. 굳이 해킹이 아니더라도 DB에 접근할 수 있는 권한을 가진 사람이 비밀번호 같이 민감한 개인정보를 모두 볼 수 있기 때문입니다.

데이터 보안을 강화하기 위해 mongoose의 미들웨어를 사용해서 비밀번호를 암호화 하여 저장하겠습니다. 데이터 암호화에는 bcrypt 라는 NPM 모듈을 사용합니다. npm i bcrypt 명령어로 설치합니다. 자세한 정보는 NPM 공식 문서를 참고해주세요. https://www.npmjs.com/package/bcrypt

암호가 DB에 저장되기 전에 mongoose 미들웨어를 사용해 해싱 후에 저장되로록 하겠습니다. 미들웨어는 User.js 파일에서 userSchema에 달아줍니다. 비밀번호를 저장하기 전에 암호화하는 절차를 만들기 위해 mongoose의 Schema.pre API를 사용합니다. DB에 특정 동작이 일어나기 전에 실행할 콜백 함수를 인자로 줄 수 있습니다.

가장 먼저 import bcrypt from "bcrypt; 를 통해 모듈을 불러옵니다. 그리고 미들웨어를 만들어줍니다.

pre 미들웨어는 지정한 동작이 모델에서 실행되기 전에 저절로 실행됩니다. 여기서는 데이터 저장 전에 실행하기 위해 "save"를 인자로 전달하였습니다.

this 는 저장할 데이터를 의미합니다. 따라서 비밀번호 데이터에 접근하고 싶다면 this.password 라고 접근할 수 있습니다.

bcrypt 공식 문서에서 가져온 사용법입니다.

bcrypt.hash(myPlaintextPassword, saltRounds, function(err, hash) {
    // Store hash in your password DB.
});

해싱 함수의 첫 번째 인자로는 해싱 대상인 비밀번호, 두 번째 인자로는 해싱 반복 횟수를 지정해줍니다. 미들웨어의 동작이 끝나면 저절로 DB 저장이 이루어지기 때문에 세 번째 인자인 콜백 함수는 주지 않겠습니다.

이제 회원가입 폼을 작성하고 DB 쉘에서 유저 객체를 확인해보면 비밀번호가 암호화되어 저장됩니다.

{
	"_id" : ObjectId("6116474bec476cfffc91f9a9"),
	"name" : "Hyuno Choi",
	"email" : "soonitoon@gmail.com",
	"username" : "soonitoon",
	"password" : "$2b$05$BYojQuz1kFXMF8Xu1.9sJuREvRUYR/SPcAYFuB7.emuMBh7ZzBp..",
	"location" : "Korea, Republic of",
	"__v" : 0
}

mongoose 경고 메시지 해결

잠깐 Node.js 콘솔에 뜨는 경고 메시지를 해결하고 넘어가겠습니다. 이 메시지는 몽고DB 버전에 따라 나타나지 않을 수도 있습니다.

(node:65532) DeprecationWarning: collection.ensureIndex is
deprecated.Use createIndexes instead.
(Use `node --trace-deprecation ...`
to show where the warning was created)

스키마에 자동으로 인덱스를 메겨주는 ensureIndex라는 기능이 곧 사라질 예정인가봅니다. 대신 useCreateIndex 를 사용하라고 합니다. 이걸 사용할 수 있도록 mongoose의 커넥션 옵션을 수정하겠습니다.

db.js 파일에서 커넥션 옵션을 넘겨줄 때 맨 마지막 줄에 useCreateIndex: true 를 추가해줍니다. 공식문서에 따르면 해당 기능을 사용한다고 해서 다른 코드를 수정할 필요는 없다고 합니다. https://mongoosejs.com/docs/deprecations.html

이렇게 로그인 기능의 기초를 다듬었습니다. 다음 포스팅에서는 계속해서 로그인 기능을 강화해보도록 하겠습니다.


<참고 문서>

profile
프론트엔드 웹 개발자를 목표로 하고 있습니다.

0개의 댓글