웹/NodeJS

[NodeJS] Express와 Sequelize-cli를 사용한 회원가입 구현

꾸적꾸적 2022. 6. 2. 13:06

 

[NodeJS] Express와 Sequelize-cli를 사용한 DB 연결

 

지난 글에서 이어진다.

 

이번에도 저번글에서 참고했다고 밝힌 아래 링크를 참조했다.

NodeJS 및 관련 공부처 :: Victolee 의 tistory 블로그

 

 


저번 시간에 DB 연결을 성공적으로 끝냈다.

 

이번에는 로그인 구현을 위해 회원가입을 진행한다.

먼저, 회원가입을 하기 위해서는,

DB에 어떻게 저장할지에 관한 설계부터 진행해야한다.

 

기본적으로 회원가입을 위하여 우리는 

 name 이름,

email 이메일,

password 비밀번호,

salt 솔트(암호화)

네가지가 필요하다.

 

위에서 참조한 링크에서는

createdAt 만들어진 날짜,

updateAt 수정된 날짜

도 추가되어 있으니 그대로 반영했다.

 

// ../migrations/create-user.js
// (이름은 앞 과정을 따라하면 create-user.js 앞에 날짜가 추가되어 있을텐데,
// 이름을 수정해도 상관은 없다.)

'use strict';
module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.createTable('users', {
      name: {
        type: Sequelize.STRING,
        allowNull: false
      },
      email: {
        type: Sequelize.STRING,
        allowNull: false,
        validate: {
            isEmail: true
        },
        primaryKey: true
      },
      password: {
        type: Sequelize.STRING,
        allowNull: false
      },
      salt:{
        type: Sequelize.STRING
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  down: (queryInterface, Sequelize) => {
    return queryInterface.dropTable('users');
  }
};

이것은 어떤 타입, 어떤 조건을 가지고 DB에 저장하는지에 관한 구문이다.

type은 다들 아는 INT STRING 과 같은 변수형

 

email 파트의 

validate:{} 구문은 다양한 구문이 안에 들어가는데,

여기서는 email 형식인지를 판별해주는 isEmail을 넣어주었다.

 

그리고 users 라는 이름의 Table로 내보내주는 return을 작성하였다.

이로써, DB에서는 obersecurity라는 이름의 DB에서

users라는 테이블이 생성되었고,

테이블의 내용은

name, email, password, salt, createAt, updatedAt이

전부 값이 없는 상태로 만들어지게 된다.


이제 DB에 들어갈 테이블 형식을 만들어 주었으니,

코드에서 그 테이블 형식에 맞추어 넣어줄 라우터와 관련된 모델을 작성해야한다.

 

// ../models/user.js

'use strict';
module.exports = (sequelize, DataTypes) => {
  var user = sequelize.define('user', {
    name: {
      type: DataTypes.STRING,
      allowNull: false
    },
    email: {
      type: DataTypes.STRING,
      allowNull: false,
      validate: {
        isEmail: true
      },
      primaryKey: true
    },
    password: {
      type: DataTypes.STRING,
      allowNull: false
    },
    salt:{
      type: DataTypes.STRING
    }
  });
  return user;
};

위에서 테이블을 작성해줄때 했던 내용을 기본으로

필요한 값들을 작성해준다.

 

설명은 위와 거의 동일하기때문에 패스.

 


이제 모델과 테이블 작성이 끝이 났으니,

서버를 실행했을때 나오는 화면을 만들어줄 차례이다.

이에 맞추어 라우터도 작성해주어야한다.

따라서 라우터 이전에 UI부터 제작한다.

 

화면은 html로 만들었다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<h3>회원 가입</h3>
<form action="/users/sign_up" method="post">
    <table>
        <tr>
            <td>이름 : </td>
            <td><input type="text" name="userName"></td>
        </tr>
        <tr>
            <td>이메일 : </td>
            <td><input type="text" name="userEmail"></td>
        </tr>
        <tr>
            <td>비번 : </td>
            <td><input type="password" name="password"></td>
        </tr>
        <tr>
            <td><input type="submit" value="회원가입"></td>
        </tr>
    </table>
</form>
</body>
</html>

 

기본적으로 이름과 이메일, 비번이 입력되고,

이후 salt는 랜덤값으로 입력될 예정이니 패스,

당연히 만들어진 날짜나 수정된 날짜도 패스.

총 세가지가 입력되도록 만들고,

마지막에 submit (회원가입) 버튼을 만들어두었다.

 

여기서 중요한점은 9번째줄에 있는 form action 파트인데,

이 파트에서 제대로 설정을 해두지 않으면

서버를 키고 난 후, 제대로된 리다이렉팅이 안되니 주의.

 


UI, 모델, 테이블 전부가 만들어졌으니,

이제 가장 중요한 라우터를 만들어줄 차례이다.

 

// ../routes/users.js

const express = require('express');
const router = express.Router();
const models = require("../models");

// localhost:3000/users(.js)/sign_up 으로 접속했을때 실행되는 구문.(GET)
// router.get으로 받았기 때문에, 위와 같이 접속된다.
router.get('/sign_up', function(req, res, next) {
  res.render("user/signup"); // 위에서 만든 UI를 렌더링 하라는 구문.
});

// localhost:3000/users/sign_up 에서 입력값이 있는 경우, 실행되는 구문. (POST)
// 현재는 값을 입력하고 버튼 클릭으로 서버로 전송될때만을 이용한다.
router.post("/sign_up", function(req,res,next){
  let body = req.body;

  models.user.create({
    name: body.userName,
    email: body.userEmail,
    password: body.password
  })
  .then( result => { // 성공했을 시, sign_up으로 리다이렉트
    res.redirect("/users/sign_up");
  })
  .catch( err => { // 실패했을 시, error 
    console.log(err)
  })
})

module.exports = router;

이런식으로 구현하면 회원가입 자체는 해결된다.

 

터미널에 npm start을 치고

localhost:3000/users/sign_up에 들어가 확인해보자..

 


하지만 아쉽게도 이렇게 끝을 낼수 없는데,

DB는 언제든지 해커들에 의해 해킹될 수 있다.

즉, 개인정보를 지키기 위해서는 비밀번호를 그대로 두지 않고,

암호화해서 저장을 해야하는데,

암호화에는 또 여러가지 방식이 있다.

이 부분은 궁금하다면 카테고리 보안쪽을 확인해볼것.

 

어쨌건, 회원가입의 경우,

키 하나만 있으면 되고, 비동기식이여도 상관이 없기때문에

해시 알고리즘 중, SHA-512 암호화를 채택했다.

 

또한 Nodejs의 경우, crypto라는 모듈을 제공하는데,

이것이 sha512 암호화를 사용하여 저장하는데 큰 도움을 준다.

 

// ../routes/users.js

// 맨 윗줄에 추가
const crypto = require('crypto');

// POST 부분 수정.
router.post("/sign_up", async function(req,res,next){
  let body = req.body;

  // inputPassword(DB에 저장할 값) 을 패스워드와 salt를 합치고
  // hashPassword에 sha512를 사용하여 제작.
  let inputPassword = body.password;
  let salt = Math.round((new Date().valueOf() * Math.random())) + "";
  let hashPassword = crypto.createHash("sha512").update(inputPassword + salt).digest("base64");

  let result = models.user.create({
    name: body.userName,
    email: body.userEmail,
    password: hashPassword,
    salt: salt
    })
    .then( result => {
      console.log("성공");
      res.redirect("/users/login");
    })
    .catch( err => {
      console.log("실패");
      console.log("/users/sign_up");
  })
})

만약 다른 블로그를 참조한다면,

위의 let hashPassword 부분 맨 뒤의 digest 부분을 잘 살펴 보길 바란다.

 

저기에는 hex나 base64등 다양한 방식으로 가능한데,

저 부분이 만약 다르다면 에러가 뜰 수 밖에 없다.

( 로그인 시 다른 방식으로 다이제스트 되어있다면 매우 높은 확률로 실패가 뜨게된다. )

필자는 base64 방식을 채택했다.

 

hex는 한글자, 1바이트를 2바이트 16진수로 표현하고,

base64는 세글자, 3바이트를 4바이트 64진수로 표현한다.

 

다시 말하면 표현 방식의 차이인데,

대부분의 블로그에서 설명을 hex로 해두었기에,

base64로 한번 해보고 싶었다.

 

사실 안전레벨에서는 어떤 차이가 있는지는 크게 깨닫지는 못했는데

배우는 입장으로서는 새로운걸 해보는게 더 좋지않을까 라는 마음으로 해보았다.

 

사실 처음에는 pbkdf2 방식을 사용하려 했으나,

이 또한 위와 같은 이유로 이렇게 사용했다.

이때는 pbkdf2 방식을 쓸지 몰랐지...

이후에 리액트에서 쓰게된다..

 

이로써 회원가입 구현은 완료했다.

다음은 회원가입한 DB 내용을 바탕으로

로그인을 구현할 예정이다.