회원가입 기능 구현하기

345·2022년 11월 30일
0

회원가입 기능을 구현해봅시다.
해야 할 일을 정리하면...

  1. User 모델 만들기 (회원 데이터 Model)
  2. 회원 가입 기능을 처리할 컨트롤러 구현하기 (DB 에 회원 정보 생성하는 기능)

위와 같습니다.

🔔 User 모델 만들기

절차를 규정하면 다음과 같습니다.

  1. models 폴더에 모델을 정의할 User.js 파일 생성
  2. init.js 파일에서 생성한 User 모델 임포트해주기
  3. User 모델은 어떤 속성으로 구성될지 규격 정하기

기본 세팅

// init.js 파일
import "./db";
import "./models/Video";
import "./models/User";
import app from "./server";

const PORT = 4000;

const handleListening = () =>
  console.log(`Server listening on http://localhost:${PORT}`);

app.listen(PORT, handleListening);

models 폴더에 User 스키마를 정의할 User.js 파일을 만든 후, init.js 파일에서 import 해줍니다.

// models/User.js 파일
import mongoose from "mongoose";

User.js 파일에서는 mongoose 를 import 해주면 준비 완료!

User 모델 정의하기

스키마를 정의하여 모델을 만들어봅시다.
기본적으로 회원이 가져야 하는 속성은 이름, 비밀번호, 이메일, 유저닉네임 등등이 있겠죠?

import mongoose from "mongoose";

const userSchema = new mongoose.Schema({
  email: { type: String, required: true, unique: true },
  username: { type: String, required: true, unique: true },
  password: { type: String, required: true },
  name: { type: String, required: true },
  location: String,
});

const User = mongoose.Model("User", userSchema);

export default User;

스키마를 정의하였습니다.
고유한 값을 가지도록 하고 싶은 속성에는 옵션에 unique: true 를 지정합니다.

🔥 회원가입 기능 구현하기

이제 User 모델을 사용하여, 사용자가 입력한 정보를 받아 새로운 user 정보를 DB 에 저장하는 작업을 설계해봅시다.

회원가입 기능은 다음과 절차를 가질겁니다.

  1. form 을 통해 유저 정보를 POST 로 넘겨받음
  2. 할당된 controller 가 request 정보를 넘겨받음
  3. request 에서 유저가 입력한 정보를 사용 가능
  4. 입력정보와 User 모델을 이용하여, User.create( ) 로 새로운 doc 을 DB 에 추가
  5. DB 에 유저 정보가 추가되면, 로그인 페이지로 리다이렉트

쉽게 말하면,
1) 정보를 받아서, 2) DB 에 추가, 3) res.redirect("/login")
의 절차를 따릅니다.

물론, 예외와 기타사항도 처리해줘야 합니다.

  • unique 한 정보의 속성값 (username, email) 이 다른 doc 의 값과 겹치지 않는지 (중복 방지)
  • 유저가 입력한 비밀번호 재확인

form 과 컨트롤러

위 고려사항을 기반으로 form 과, 입력 정보를 처리할 컨트롤러를 구성해보았습니다.

{% if errorMessage %}
<span>{{errorMessage}}</span>
{% endif %}
<form method="POST">
  <input placeholder="Name" type="text" name="name" required />
  <input placeholder="Email" type="email" name="email" required />
  <input placeholder="Username" type="text" name="username" required />
  <input placeholder="Password" type="password" name="password" required />
  <input
    placeholder="Confirm Password"
    type="password"
    name="password2"
    required
  />
  <input placeholder="Location" type="text" name="location" required />
  <input type="submit" value="join" />
</form>

Nunjucks 템플릿 엔진 사용을 기반으로 구성한 form 입니다.
password 입력은 두 개를 받아서, 두 값이 일치하는지 확인합니다.

에러가 있을 경우 errorMessage 를 보내서 오류를 표시합니다.

가입 기능을 처리하기 위한 컨트롤러를 만듭니다.
/join 라우터로 GET 요청이 오면 getJoin 컨트롤러가, POST 요청이 오면 postJoin 컨트롤러가 처리합니다.

getJoin 은 단순하게 뷰를 로딩해주는 기능만 하고있는데, postJoin 은 입력 정보를 받아 DB 에 추가해주고 로그인 페이지로 리다이렉팅 해줍니다.

express - res.redirect( ) 문서

import User from "../models/User";

export const getJoin = (req, res) =>
  res.render("join.html", { pageTitle: "Join" });

export const postJoin = async (req, res) => {
  const { name, username, email, password, password2, location } = req.body;
  // form 에서 입력 정보를 받아옴
  const pageTitle = "Join";

  // password 를 맞게 썼는지 확인
  if (password !== password2) {
    return res.render("join.html", {
      pageTitle,
      errorMessage: "Password confirmation does not match.",
    });
  }

  // 입력한 정보와 동일한 username 이나 email 을 가진 유저가 있는지 확인
  const userExists = await User.exists({ $or: [{ username }, { email }] });
  if (userExists) {
    return res.render("join.html", {
      pageTitle,
      errorMessage: "This username / email is already taken.",
    });
  }

  // 새로운 유저를 DB 에 추가
  await User.create({
    name,
    username,
    email,
    password,
    location,
  });

  // 로그인 페이지로 이동
  return res.redirect("/login");
};

와! 회원가입을 할 수 있게 되었습니다.
하지만 여기에는 치명적인 문제가 있습니다.

❗ 비밀번호 암호화

위 코드대로 진행을 하면, 내가 입력한 정보가 그대로 DB 에 저장되겠죠.
바로 그게 문제입니다. 유저의 모든 정보 가 그대로 드러나게됩니다.

데이터베이스의 내용을 보는 건 시스템 관리자라면 아주 쉬운 일입니다. 어떤 서비스에 가입했는데 개발자가 내 정보를 다 뜯어보고 내 비밀번호도 아이디도 다 알고... 이러면 안되겠죠.

보통 사람들은 한 사이트에서 쓰는 비밀번호를 다른 사이트에서도 그대로 사용하기 때문에 하나에서 털렸다면? 다른 정보도 줄줄이 털리게 됩니다...

이러면 막대한 손해가 나고 아주 위험하기 때문에 반드시!! 비밀번호를 암호화하는 과정이 필요합니다.

✅ 해시 함수

비밀번호 암호화란, 내가 입력한 비밀번호를 그대로 DB 에 저장하지 않고 다른 문자열로 변형하여 DB 에 저장하는 것을 의미합니다. 그렇게해서, 누가 DB 를 뜯어봐도 나의 원래 비밀번호가 무엇인지 알 수 없게 하는것이죠.

암호화를 위해서 Hash 함수 (해시 함수) 를 사용합니다.

해시 함수란❓

  • 받아온 입력을 특정한 문자열로 반환해주는 함수
    • 동일한 입력은 동일한 결과를 출력
    • 다른 입력은 반드시 다른 결과를 출력
    • 출력 결과로 입력을 유추할 수 없음

즉, 해시 함수란 내 원래 비밀번호를 받아서 아주 이상한 문자열로 바꿔주는 함수입니다.

Hash(password) = 23sdf?3$sdfkjn224234 (암호화된 비밀번호)

이렇게... 아주 괴상한 문자열이 나옵니다.
중요한 건, 동일한 입력은 동일한 출력을 낳고, 다른 입력을 하면 다른 출력이 나온다는 것입니다.

이는 해시값(암호화된 비밀번호) 을 이용하면, 원래 비밀번호를 몰라도 해시 알고리즘(비밀번호를 암호화하는 과정) 을 이용하여 비밀번호 확인을 할 수 있다는 것입니다. 물론 출력 결과를 역으로 변환하여 원래 비밀번호를 알아내는 것은 매우 어렵습니다.


결론은 다음과 같습니다.

  • DB 에 비밀번호를 그대로 저장하면 안 된다!
  • 비밀번호를 저장하기 전에 반드시 해싱(암호화) 과정을 거치자!

🔒 비밀번호 해싱하기

암호화를 위한 라이브러리를 이용합시다.

npm install bcrypt

bcrypt 라는 해싱 암호화 라이브러리를 설치합니다.

bcrypt - npm 가이드

우리가 해줘야 할 일은, 유저가 입력한 정보를 DB 에 저장하기 전에, 비밀번호 정보를 해싱하여 DB 에 저장하는 것입니다.

이를 위해, "save" 이벤트에 발동하는 미들웨어 를 User 스키마에 추가해봅시다.

import bcrypt from "bcrypt"; // 라이브러리 임포트
import mongoose from "mongoose";

.
. // User 스키마 구성 내용
.

// 미들웨어
// User doc 을 create 할 때, 저장 전에 해싱!
userSchema.pre("save", async function () {
  this.password = await bcrypt.hash(this.password, 5);
  // this.password 를 해싱
  // saltRound 가 5
});

const User = mongoose.model("User", userSchema);

export default User;

hash 함수를 이용하여 비밀번호를 DB 에 저장하기 전에 암호화해줬습니다.
유저의 비밀번호가 악용되는 것을 막았습니다!

profile
기록용 블로그 + 오류가 있을 수 있습니다🔥

0개의 댓글