![article thumbnail image](https://blog.kakaocdn.net/dn/cfq9FR/btrGhAm1uPJ/QSxZ2c81rS2zCw1G7A15X0/img.png)
반응형
소개
보일러 플레이트란?
- 웹사이트를 만들 때, 로그인, 회원가입 등등… 통상적으로 들어가는 기능들이 존재한다.
- 어떠한 프로젝트를 만들 때 0부터 직접 만드는 게 아니라, 자주 쓰이는 기능을 만들어서 어디든 재사용할 수 있게 한다.
Node.js와 Express.js 다운로드하기
Node.js
- 기존 JavaScript는 브라우저에서만 동작했었다.
- Node.js를 통해 Chrome이나 Internet Explorer에 국한된 게 아닌, 서버 사이드(server-side)에서도 쓸 수 있게 되었다.
Express.js
- Node.js가 자동차 엔진이라면, 그 엔진을 가지고 자동차의 바퀴도 만들고, 브레이크도 만들고…
- 자동차를 만드는 것이 Express.js.
- Node.js를 좀 더 쉽게 이용할 수 있는 프레임워크.
프로젝트 설정
- package.json에 express라고 추가되었음! 다운로드 할 때 –save라고 지정했는데… 이게 표시가 된다.
- 이 애플리케이션에서는 해당 기능을 사용하겠다, 라고 표시하는 것이다.
- node_modules라는 폴더 안에 다운 받을 dependency 라이브러리들이 모두 이 폴더에 있다. 여기는 수정할 일이 거의 없음.
MongoDB 연동
- Mongoose: 몽고디비를 편하게 쓸 수 있는 툴
다음 라인은 지워 주면 된다. MongoDB 6 버전 이상부터는 기본 값이어서 설정하지 않아도 된다.
useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, useFindAndModify: false
>> MongoParseError: options usecreateindex, usefindandmodify are not supported
MongoDB Model & Schema
- Model: Schema를 감싸 주는 역할
- Schema: 상품에 관련된 글을 작성한다고 하면, 그 글을 작성한 사람이 누구인지, 작성할 때 포스트의 이름이 뭔지, 포스트 이름 타입이 뭐고, 최대 길이는 몇인지… 하나하나의 정보를 지정해 줄 수 있다.
BodyParser & PostMan & 회원가입
- Client - 브라우저에서 회원가입을 하려고 하면… 이 클라단에서 이메일 비밀번호 작성해서 서버에 보냄
- 서버에서 받을 때 필요한 것들은 Body Parser를 이용한다. 예를 들면 이름, 이메일 등을 받을 수 있음.
npm install body-parser --save
- 현재로선 클라이언트를 만들어 둔 게 없으니 postman을 이용한다. > postman
Postman API Platform | Sign Up for Free
Postman is an API platform for building and using APIs. Postman simplifies each step of the API lifecycle and streamlines collaboration so you can create better APIs—faster.
www.postman.com
NodeMon
- 원래대로라면 이 Node 서버를 내리고 다시 켜야 반영이 되는데, NodeMon 라이브러리를 이용하면 내리고 올리지 않아도 소스 변화를 감지해서 반영시켜 준다.
npm install nodemon --save
환경별 URI 설정
- 환경별 config를 추가하여 dev, production이 각각 다른 DB를 바라보게 한다.
- 또한 MongoDB URI가 외부로 노출되는 일이 없게 한다. (Git 등…)
Bcrypt
- Bcrypt를 이용하여 패스워드를 암호화한다.
npm install --save bcrypt
// sava 하기 전, function을 실행함
userSchema.pre('save', function(next){
var user = this;
if (user.isModified('password')) {
// 비밀번호를 암호화한다.
// salt를 이용해서 비밀번호를 암호화하며, 이 salt를 생성해야 한다.
// saltRounds는 salt가 몇 글자인지를 나타낸다.
bcrypt.genSalt(saltRounds, function(err, salt) {
if (err) return next(err)
bcrypt.hash(user.password, salt, function(err, hash) {
if (err) return next(err)
user.password = hash
next()
})
})
} else {
// 비밀번호가 아니라 다른 정보를 변경한다면 넘겨 준다.
next()
}
})
- pre 함수는 매개변수로 입력한 ‘save’ 함수가 실행되기 전, function을 실행하게끔 한다.
next()
는 그 다음 함수로 넘어가는 함수.
![](https://blog.kakaocdn.net/dn/1FRMm/btrF9goXs0y/BdlA3cAu1UK5Mjr9w80kyK/img.png)
반응형
로그인 기능
이메일 유효성 검사, password 비교
app.post('/login', (req, res) => {
// 요청된 이메일을 데이터베이스에 있는지 찾는다.
User.findOne({ email: req.body.email }, (err, user) => {
if (!user) {
return res.json({
loginSuccess: false,
message: "제공된 이메일에 해당하는 유저가 없습니다."
})
}
// 요청된 이메일이 데이터베이스에 있다면, 비밀번호가 맞는지 확인한다.
user.comparePassword(req.body.password, (err, isMatch) => {
if (!isMatch) return res.json ({ loginSuccess: false, message: "비밀번호가 틀렸습니다."})
// 비밀번호까지 맞다면, 토큰을 생성한다.
user.generateToken((err, user) => {
// 후술
})
})
})
})
- plain text로 들어온 입력을 암호화하여 비교해 준다. 이때,
bcrypt.compare()
를 사용한다.
userSchema.methods.comparePassword = function(plainPassword, cb) {
// plainPassword 1234567 암호화된 비밀번호 $2b$10...
bcrypt.compare(plainPassword, this.password, function(err, isMatch) {
if (err) return cb(err);
cb(null, isMatch)
})
}
Token 생성
- 로그인이 성공하면 토큰을 생성한다.
- jsonwebtoken 라이브러리를 사용한다. > jsonwebtoken
- json web token library
npm install jsonwebtoken --save
jwt.sign()
을 이용하여 user._id를 포함한 토큰을 생성한다.- 이 토큰을 파싱하여 어떤 유저인지(
user._id
)를 파악할 수 있다.
userSchema.methods.generateToken = function(cb) {
// jsonwebtoken을 이용하여 token을 생성한다.
var user = this;
// user._id + 'secretToken' = token
// 'secretToken' -> user._id
var token = jwt.sign(user._id.toHexString(), 'secretToken')
user.token = token
user.save(function(err, user) {
if (err) return cb(err)
cb(null, user)
})
}
- 만들어진 토큰을 쿠키 또는 로컬 스토리지에 저장한다. 각각의 장단점이 존재하기 때문에 어디에 저장할지 선택해야 한다. 이번에는 쿠키에 저장해 본다.
- cookie-parser라는 라이브러리를 사용한다.
npm install cookie-parser --save
app.post('/login', (req, res) => {
// 요청된 이메일을 데이터베이스에 있는지 찾는다.
User.findOne({ email: req.body.email }, (err, user) => {
if (!user) {
return res.json({
loginSuccess: false,
message: "제공된 이메일에 해당하는 유저가 없습니다."
})
}
// 요청된 이메일이 데이터베이스에 있다면, 비밀번호가 맞는지 확인한다.
user.comparePassword(req.body.password, (err, isMatch) => {
if (!isMatch) return res.json ({ loginSuccess: false, message: "비밀번호가 틀렸습니다."})
// 비밀번호까지 맞다면, 토큰을 생성한다.
user.generateToken((err, user) => {
if (err) return res.status(400).send(err);
// 토큰을 저장한다. 어디에? 쿠키, 로컬 스토리지...
res.cookie("x_auth", user.token)
.status(200)
.json({ loginSuccess: true, userId: user._id })
})
})
})
})
![](https://blog.kakaocdn.net/dn/qF0C7/btrF9SgQJln/iks7iDTwfRnc4uRefuboS0/img.png)
Auth 기능
Auth가 필요한 이유
- 페이지마다 권한이 다를 수 있다. 글 쓰기 등 어떠한 페이지는 로그인이 되어 있어야 하고, 관리자만 접근할 수 있는 페이지 또한 존재할 수 있다.
- 따라서 페이지 이동할 때마다 로그인이 되어 있는지, 관리자 유저인지 등을 체크해야 한다.
- 글을 쓰거나 지울 때, 권한이 있는지 체크되어야 한다.
대략적인 과정
- 서버에서 쿠키에 저장된 Token을 가져온다.
- Encoded Token을 Decode하면
user._id가 나온다.
- 이 id가 DB에 있다면 이 토큰은 유효하다고 서버에서 판단한다.
구현
- middleware: end point에서 req를 받은 다음, callback function 호출하기 전에 처리해 준다.
middleware/auth.js
생성
let auth = (req, res, next) => {
// 각종 인증 처리를 진행한다.
}
module.exports = { auth };
auth.js
const { User } = require("../models/User");
let auth = (req, res, next) => {
// 각종 인증 처리를 진행한다.
// 클라이언트 쿠키에서 토큰을 가져온다.
let token = req.cookies.x_auth;
// 토큰을 복호화한 후, 유저를 찾는다.
User.findByToken(token, (err, user) => {
if (err) throw err;
if (!user) return res.json({ isAuth: false, error: true })
req.token = token;
req.user = user;
next() // next가 없으면 middleware에 갇히니 주의한다!
})
// 유저가 있으면 인증 OK
// 유저가 없으면 인증 NO!
}
module.exports = { auth };
User.js
userSchema.statics.findByToken = function(token, cb) {
var user = this;
// 토큰을 decode 한다.
// user._id + '' = token
jwt.verify(token, 'secretToken', function(err, decoded) {
// 유저 아이디를 이용하여 유저를 찾은 후,
// 클라이언트에서 가져온 token과 DB에 보관된 token이 일치하는지 확인한다.
user.findOne({ "_id" : decoded, "token": token }, function(err, user) {
if (err) return cb(err)
cd(null, user)
})
})
}
index.js
const { auth } = require('./middleware/auth')
// ...
app.get('/api/users/auth', auth, (req, res) => {
// 여기까지 미들웨어를 통과해 왔다 => Authentication이 true라는 뜻
res.status(200).json({
_id: req.user._id,
isAdmin: req.user.role === 0 ? false : true,
isAuth: true,
email: req.user.email,
name: req.user.name,
lastName: req.user.lastName,
role: req.user.role,
image: req.user.image
})
})
로그아웃 기능
- 로그아웃 Route를 만들어서, 로그아웃 하려는 유저를 DB에서 찾아서 그 유저의 토큰을 지워 준다.
- Auth를 구현할 때 클라이언트단에 있는 토큰과 DB에 있는 토큰을 비교하는데, DB에 있는 토큰을 지워 버리면 토큰 비교는 항상 실패하게 될 것이다. 따라서 로그아웃 시 DB의 토큰을 지우면 된다.
app.get('/api/users/logout', auth, (req, res) => {
User.findOneAndUpdate({ _id: req.user._id },
{ token: ""},
(err, user) => {
if (err) return res.json({ success: false, err});
return res.status(200).send({
success: true
})
})
})
![](https://blog.kakaocdn.net/dn/nBmz7/btrGb8bTDCQ/s59Q22e4ZDnnEoKCuLVxOK/img.png)
![](https://blog.kakaocdn.net/dn/Zsb9m/btrGarQOF8Z/0mvKRdOySN5i1IZFbphO1K/img.png)
- 로그아웃 성공 후, DB의 토큰이 사라져 있다.
![](https://blog.kakaocdn.net/dn/4JiVd/btrGcVwCW3W/9MbxNs38W1FyBevdzBwNv1/img.png)
해당 포스트는 다음의 강의를 수강하며 개인 기록용으로 메모하였습니다. 양질의 지식을 제공해 주신 John Ahn 님께 감사드립니다.
[무료] 따라하며 배우는 노드, 리액트 시리즈 - 기본 강의 - 인프런 | 강의
이 강의를 통해서 리액트와 노드를 어떻게 사용하는지 기본적인 내용들을 배울 수 있습니다., - 강의 소개 | 인프런...
www.inflearn.com
반응형