회원가입 기능을 구현해봅시다.
해야 할 일을 정리하면...
위와 같습니다.
절차를 규정하면 다음과 같습니다.
// 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 해주면 준비 완료!
스키마를 정의하여 모델을 만들어봅시다.
기본적으로 회원이 가져야 하는 속성은 이름, 비밀번호, 이메일, 유저닉네임 등등이 있겠죠?
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) 정보를 받아서, 2) DB 에 추가, 3) res.redirect("/login")
의 절차를 따릅니다.
물론, 예외와 기타사항도 처리해줘야 합니다.
위 고려사항을 기반으로 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 에 추가해주고 로그인 페이지로 리다이렉팅 해줍니다.
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 (암호화된 비밀번호)
이렇게... 아주 괴상한 문자열이 나옵니다.
중요한 건, 동일한 입력은 동일한 출력을 낳고, 다른 입력을 하면 다른 출력이 나온다는 것입니다.
이는 해시값(암호화된 비밀번호) 을 이용하면, 원래 비밀번호를 몰라도 해시 알고리즘(비밀번호를 암호화하는 과정) 을 이용하여 비밀번호 확인을 할 수 있다는 것입니다. 물론 출력 결과를 역으로 변환하여 원래 비밀번호를 알아내는 것은 매우 어렵습니다.
결론은 다음과 같습니다.
암호화를 위한 라이브러리를 이용합시다.
npm install bcrypt
bcrypt
라는 해싱 암호화 라이브러리를 설치합니다.
우리가 해줘야 할 일은, 유저가 입력한 정보를 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 에 저장하기 전에 암호화해줬습니다.
유저의 비밀번호가 악용되는 것을 막았습니다!