Lsiron
HELLO FOLIO(express, mongoose)- 3(bcrypt를 이용한 회원가입 구현) 본문
현재 폴더 구조
web_project/
├── db/
│ ├── model # 각종 모델 폴더
│ ├── schemas # 각종 스키마 폴더
│ └── index.js # db 연결파일
├── middleware/ # 각종 middleware 폴더
├── passport/ # 로그인 관련 passport 폴더
│ ├── strategies # 전략 구성
│ └── index.js # 전략 exports 파일
├── routes/ # 라우트 관련 폴더
│ └── index.js # 라우트 exports 파일
├── services/ # 유저 관련 service 폴더
├── views/ # EJS 템플릿 폴더
├── public/ # static 파일 폴더
│ ├── css/ # CSS 파일들
│ ├── js/ # JS 파일들
│ └── images/ # 이미지 파일들
├── .env # 환경 변수 파일
├── .gitignore # Git 무시 파일
├── package.json # 프로젝트 메타데이터 및 종속성 목록
├── app.js # 애플리케이션 진입점
├── index.js # 서버 실행 파일
└── README.md # 프로젝트 설명 파일
현재 설치한 모듈
"dependencies": {
"dotenv": "^16.4.5",
"express": "^4.19.2",
"mongodb": "^6.7.0",
"mongoose": "^8.4.3",
}
DB와 실행파일도 다 만들었으니, 이제 본격적으로 시작해보자.
허나 시작하기에 앞서 터미널에 매번 $ npm start index.js 를 입력해주기는 번거로웠다.
때문에 파일을 저장만 하면 자동으로 서버를 실행시켜주는 nodemon 을 사용하도록 하자.
먼저 $ npm i -g nodemon 으로 라이브러리를 설치해준다. (-g 를 입력해주는 이유는 그냥 npm i nodemon 으로 설치하면 작동 하질 않는다.)
한번 설치해주고 터미널에 $ nodemon index.js 를 입력하면 터미널을 끄거나 오류가 발생하지 않는 이상 자동으로 서버가 실행된다.
또한 이제는 원격저장소에 올려야 하기 때문에 gitignore 파일에 무엇을 올리지 않을지 입력해 준다.
https://www.toptal.com/developers/gitignore
위 사이트는 내가 사용하는 언어 등을 입력했을 때 무엇을 gitignore 파일에 입력해야하는지 알려준다 참고하도록 하자.
자 이제 로그인 및 회원가입을 만드려면 해당 HTML 화면이 필요할 것이다. 나는 지금 res.send('안녕하십니까')로 그저 글자만 보이게 해 놓았을 뿐이다.
허나 HTML을 사용하지 않고 HTML + JS 로 사용하기 아주 간편한 템플릿 엔진 EJS를 사용하도록 하겠다.
EJS 를 사용하기 위해 먼저 터미널에 $ npm i ejs 를 입력하여 라이브러리를 설치 해 주도록 하자.
설치 했다면 바로 사용하기 위해 app.js 파일로 가서 아래 코드를 입력해주자.
app.set('view engine', 'ejs');
폴더 구조에서도 보면 알다시피 EJS 파일은 통상적으로 views 폴더에 보관한다.
views 폴더로 가서 home.ejs로 파일을 만들어주고 html 처럼 꾸며준다.
만든김에 회원가입을 위한 register.ejs 파일과 로그인에 성공시 접속할 myPage.ejs 또한 만들어주자.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="/css/home.css">
</head>
<body>
</body>
</html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="/css/register.css">
</head>
<body>
</body>
</html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="/css/myPage.css">
</head>
<body>
</body>
</html>
이제 app.js 파일로 가보자 첫 날엔 GET method만을 사용하여 조회만 가능하도록 하였고, res.send로 문자열을 보여주도록 했는데, 이제 ejs 파일도 있으니 res.render('home.ejs')로 바꾸고 ejs파일을 보여주도록 하자.
(render는 보통 ejs같은 템플릿 엔진을 보여줄 때 사용하며, ejs만 보여줄때는 인자를 하나만 적어도 되나, 나중에 ejs로 변수를 넘겨줄땐 첫 번째 인자에는 ejs 파일, 두 번째 인자에는 넘겨줄 변수를 적으면 된다.)
회원가입 페이지와 마이페이지도 만들었으니, 조회가 가능하도록 바꿔주자.
const express = require('express');
const app = express();
app.get('/home', (req, res) => {
res.render('home.ejs')
})
app.get('/register', (req, res) => {
res.render('register.ejs')
})
app.get('/my-page', (req, res) => {
res.render('myPage.ejs')
})
module.exports = { app };
이제 /home 와 /register, /myPage 경로를 들어갔을때 home.ejs 와 register.ejs, myPage.ejs 에서 설정한 화면이 보여지게 된다.
이제 로그인과 회원가입 화면을 만들었으니 기능을 만들어야겠다. (myPage는 나중에 작업할 예정)
데이터를 생성해주기 위해 POST method를 사용하여 API를 만들어보자
const express = require('express');
const app = express();
app.get('/home', (req, res) => {
res.render('home.ejs')
})
app.post('/home', (req, res) => {
console.log('로그인 완료')
})
app.get('/register', (req, res) => {
res.render('register.ejs')
})
app.post('/home', (req, res) => {
console.log('회원가입 완료')
})
app.get('/my-page', (req, res) => {
res.render('myPage.ejs')
})
module.exports = { app };
이런식으로만 설정해 놓으면 당연히도 로그인과 회원가입이 안된다. 그저 method와 경로만 설정했을 뿐 console.log()에 입력한 문자열만 출력 될 것이다. 허나, 이 method와 경로를 설정했다는 것을 기억 해 두자.
로그인 과 회원가입 화면, 그리고 API 기본을 만들었으니 db폴더의 schemas 폴더로 가서 userSchema를 만들어야겠다.
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
name: {
type: String,
required: true,
},
{
collection: "users",
timestamps: true,
});
module.exports = UserSchema;
먼저 Schema 를 사용하기 위해 mongoose를 불러와주고, 유저와 관련한 UserSchema를 만들어준다. 회원가입을 위해서 name도 만들어보자.
type은 모두 String으로 해 주었고, 모두 문서가 저장될 때 필수 값이기 때문에 required로 해 주었으며, email은 중복되면 안되므로 unique를 true로 해 주었다.
나는 Model별로 파일을 따로 만들 것 이기 때문에 UserSchema를 내보내 주도록 했다.
아래는 model 폴더 안의 UserModel이다.
const mongoose = require('mongoose');
const UserSchema = require('../schemas/userSchema');
const UserModel = mongoose.model('User', UserSchema); // 가져온 UserSchema 모델화
module.exports = UserModel;
UserModel을 내보내주었으니, 이 점을 잘 기억하고 있자.
이제 views 폴더로 가서 ejs파일에 로그인과 회원가입을 할 수 있도록 요소를 넣어주어야 한다.
먼저 home.ejs 이다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="/css/home.css">
</head>
<body>
<!-- 로그인 폼 구현 -->
<form action="/login" method="post">
<input name="email" type="text" placeholder="email">
<input name="password" type="password" placeholder="password">
<button type="submit">로그인</button>
</form>
</body>
</html>
로그인 형식을 만들어 주기위해 form 태그를 이용하였다.
form 태그에 action 속성은 "/login" 으로 method 속성은 "post" 로 입력 해 주었다. action은 로그인을 위한 url경로이며 method는 로그인 방법이다. form태그를 이용하여 로그인을 하기 위해선 action과 method는 필수이다. 또한 input의 name 속성은 값을 서버로 전달 해 주기위한 속성이다.
로그인 화면을 만든김에 회원가입 화면도 같이 만들어보자 아래는 register.ejs 이다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="/css/register.css">
</head>
<body>
<!-- 회원가입 폼 구현 -->
<form action="/register" method="post">
<input name="name" type="text" placeholder="name">
<input name="email" type="text" placeholder="email">
<input name="password" type="password" placeholder="password">
<input name="confirmPassword" type="password" placeholder="confirmPassword">
<button type="submit">회원가입</button>
</form>
</body>
</html>
confirmPassword는 비밀번호 확인 칸이다. 똑같이 form 태그에 action 속성은 "/register" 으로 method 속성은 "post" 로 입력 해 주었다.
이제 Schema와 Model을 만들었고 ejs에 요소까지 모두 넣어주었다. 그럼 이제 app.js로 API를 완성시키러 가보자.
그 전에 회원가입을 하면서 유저 정보를 저장 할 때 운영자가 DB에서 유저의 비밀번호를 볼 수 있다면 이게 해킹이 아닐까?
그럼 우리는 비밀번호를 암호화 시켜야 한다! 이를 위해서 라이브러리를 하나 설치해보자.
바로 비밀번호를 암호화 시켜주는 bcrypt 라이브러리 이다. $ npm i bcrypt 를 명령어로 입력하여 설치를 해준다.
그리곤 app.js에 bcrypt 라이브러리를 불러와보자.
const express = require('express');
const app = express();
const bcrypt = require('bcrypt');
app.get('/home', (req, res) => {
res.render('home.ejs')
})
app.post('/home', (req, res) => {
console.log('로그인 완료')
})
app.get('/register', (req, res) => {
res.render('register.ejs')
})
app.post('/home', (req, res) => {
console.log('회원가입 완료')
})
app.get('/my-page', (req, res) => {
res.render('myPage.ejs')
})
module.exports = { app };
이제 비밀번호를 암호화 시켜주는 bcrypt를 사용할 수 있게 되었다. 본격적으로 API를 만들어보자.
또한, ejs에서 보낸 body 값을 서버에서 다루기 위해서는 express에서 미들웨어를 사용해야한다.
바로 app.use(express.json()); 와 app.use(express.urlencoded({ extended: false })); 이다 이 코드를 app.js에 입력 해 주자.
const express = require('express');
const app = express();
const bcrypt = require('bcrypt');
app.use(express.json()); // 유저가 데이터를 서버로 보내면 쉽게 꺼내 쓸 수 있도록 하는 코드 , 서버에서 req.body 쓰려면 필요함
app.use(express.urlencoded({ extended: false })); // 유저가 데이터를 서버로 보내면 쉽게 꺼내 쓸 수 있도록 하는 코드 , 서버에서 req.body 쓰려면 필요함
app.get('/home', (req, res) => {
res.render('home.ejs')
})
app.post('/home', (req, res) => {
console.log('로그인 완료')
})
app.get('/register', (req, res) => {
res.render('register.ejs')
})
app.post('/home', (req, res) => {
console.log('회원가입 완료')
})
app.get('/my-page', (req, res) => {
res.render('myPage.ejs')
})
module.exports = { app };
외울 필요는 없다 그냥 클라이언트에서 보낸 값을 서버에서 처리하기 위해 이게 필요하다 만 알면되고 코드는 npm 사이트에 나와있다.
그럼 이제 로직을 작성해보자.
const express = require('express');
const app = express();
const bcrypt = require('bcrypt');
const UserModel = require('./db/model/userModel'); // db파일의 models에서 User class 불러오기
app.use(express.json()); // 유저가 데이터를 서버로 보내면 쉽게 꺼내 쓸 수 있도록 하는 코드 , 서버에서 req.body 쓰려면 필요함
app.use(express.urlencoded({ extended: false })); // 유저가 데이터를 서버로 보내면 쉽게 꺼내 쓸 수 있도록 하는 코드 , 서버에서 req.body 쓰려면 필요함
app.get('/home', (req, res) => {
res.render('home.ejs')
})
app.post('/login', async (req, res, next) => {
const { email, password } = req.body;
const user = await UserModel.findOne({ email });
if (!user) {
console.log('등록된 사용자가 아닙니다.');
}
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
console.log('비밀번호가 일치하지 않습니다.');
}
res.redirect('/my-page');
});
app.get('/register', (req, res) => {
res.render('register.ejs')
})
app.post('/register', async (req, res, next) => {
const { email, password, name } = req.body;
// 사용자가 이미 존재하는지 확인
const checkingUser = await UserModel.findOne({ email });
if (checkingUser) {
console.log('이미 존재하는 사용자입니다');
}
// 비밀번호를 해싱함
const hashedPassword = await bcrypt.hash(password, 10); // 첫번째 인자는 password 변수 두번째 인자는 몇번 꼬을지 횟수
// 새로운 사용자 생성
const newUser = {
email,
password : hashedPassword,
name,
};
await UserModel.create( newUser );
res.redirect('/login');
});
app.get('/my-page', (req, res) => {
res.render('myPage.ejs')
})
module.exports = { app };
먼저 UserModel을 불러오기 위한 코드를 입력해준다. 변수를 UserModel로 선언한 뒤에 require()안으로 경로를 입력해준다.
회원가입부터 만져보자. input 태그에서 name 속성으로 인해 email과 password, name 값이, action 경로 및 method 로 인해 body값으로 받을 수 있게 되었다. 이 값을 다뤄보자.
(원래는 ejs에서 받아온 값을 표현하는 것으로,
const email = req.body.email;
const password = req.body.password;
const name = req.body.name
이렇게 적는다. req.body.email 여기서 email이 input태그의 name 속성 값이다.
하지만, 구조분해할당으로 const { email, password, name } = req.body; 이렇게 한 줄로 줄여주었다.)
먼저 이미 가입한 회원이 있는지 찾으려면 UserSchema에서 unique 키와 값을 주었던 email로 찾아보자. mongoose 함수 findOne() 을 통해, UserModel.findOne으로 찾을 수 있다.
(findOne() 안에 { email } 이렇게 객체로 입력해준 이유는? 원래 { email : email } 이렇게 적어야 한다.
왼쪽은 UserSchema에서 설정한 email 필드, 오른쪽은 register.ejs에서 input 태그안의 name 값을 받아서 변수로 생성한 email. 허나 왼쪽과 오른쪽이 같을경우엔 { email } 이렇게 하나로 줄여서 쓸 수 있다. 그러니까 필드 값과 body 값이 일치하는지 찾는다는 뜻)
조건문을 사용하여 만약 등록된 유저가 있다면 console.log()로 찍어준다. 이 후, 등록된 유저가 없다면 유저를 등록하자.
비밀번호를 암호화 시키지 않는다면 그냥 create함수를 통해 UserModel.create로 유저를 생성해주면 되지만, 우리는 암호화 시키는 과정을 추가해야한다.
const hashedPassword = await bcrypt.hash(password, 10);
bcrypt 라이브러리에 내장된 함수 hash를 이용하여 password를 암호화 해 준다. 첫번째 인자는 암호화 해줄 값 즉, password 변수 그리고 두 번째 인자는 비밀번호를 몇번 꼬와줄지 정하는 것이다.
( 첫 번째 인자는 왜 password로 입력하지 않나요? 하면 bcrypt의 hash 함수에서 password 변수는 그 자체로 문자열이어야 한다. 때문에 객체가 아닌 password 변수만 넣어준 것. 그냥 mongoose 함수 사용할때만 객체형태로 쓴다고 알아두자.)
그리곤 newUser 변수를 생성해서 객체 값을 넣어준다. 위에서 말 했듯이 왼쪽은 필드, 오른쪽은 변수이다. create함수에 newUser 객체를 넣어주면 유저 생성이 완료되고, res.redirect('/login')을 통해 로그인 화면으로 이동한다. (redirect는 페이지를 이동시켜주는 것.)
이제 로그인을 살펴보자.
회원가입과 똑같이 home.ejs에서 email과 password를 body 값으로 받아온다.
findOne({ email }) 을 통해 변수를 생성한뒤 데이터를 확인하고 , 이 데이터가 있는지 없는지 if(!user)조건문을 통해 확인 할 수 있다.
이 후, bcrypt에 내장된 compare 함수를 통해 UserSchema 의 password 필드 값과 body 값으로 받은 password가 일치하는지 조건문으로 확인 한다.
모두 이상이 없다면 res.redirect('/myPage')를 통해 myPage.ejs 화면이 보이도록 한다.
참고로 mongoose 내장함수와 bcrypt 내장함수를 사용할 땐 비동기 처리를 해주어야 하기 때문에 async await을 써준다.
즉, 이 함수가 처리 되기까지 다음 코드를 실행하지 마세요 라는 뜻 이다.
'개발일지 > HELLO FOLIO' 카테고리의 다른 글
HELLO FOLIO(express, mongoose) - 6(jwt 을 이용한 토큰 검증 미들웨어 구현) (0) | 2024.07.02 |
---|---|
HELLO FOLIO(express, mongoose)- 5(jwt를 이용한 토큰 발급 로그인 구현) (0) | 2024.07.02 |
HELLO FOLIO(express, mongoose)- 4(passport를 이용한 로그인 전략 구현) (0) | 2024.06.29 |
HELLO FOLIO(express, mongoose)- 2(MongoDB,Mongoose 연결 구현) (0) | 2024.06.27 |
HELLO FOLIO(express, mongoose)- 1(기초 및 기획) (0) | 2024.06.22 |