[Node.js] postman을 이용하여 회원가입 기능 구현하기
- 명령 프롬프트 창에 node 설치
- node.js의 프레임워크인 express 설치
- mongoDB, mongoose 설치
- ssh 키 생성하여 github에 ssh키 설정
- 서버올리기 : npm start run ( run은 package.json에서 내가 임의로 설정해줌)
- nodemon(서버 변경상태 실시간 감지) : npm start backend ( backend는 package.json에서 내가 임의로 설정해줌)
<기본 세팅>
package.json
- 설치된 패키지들을 관리
- start시, index.js가 먼저 실행되게 함
{
"name": "boiler-plate",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "///",
"license": "ISC",
"dependencies": {
"express": "^4.17.2",
"mongoose": "^6.2.1"
}
}
index.js - mongoDB 연결 부분
-connect()부분에 해당 코드를 입력하면 db랑 연결됨
-정상적으로 연결되면, 웹 브라우저에 localhost:5000/입력시 'hello world' 출력
-console.log를 통해 콘솔창에도 출력됨
const express = require('express')
const app = express()
const port = 5000
const mongoose = require('mongoose')
mongoose.connect('mongodb+srv://USERNAME:1234@boilerplate.abcmn.mongodb.net/myFirstDatabase?retryWrites=true&w=majority').then(() => console.log('MongoDB connected...'))
.catch(err => console.log(err))
app.get('/', (req,res) => res.send('Hello World'))
app.listen(port, () => console.log(`Example app listening on port ${port}`))
User.js
- schema : 정보를 나타냄
ex) 회원가입시, 필요한 정보들을 지정함 : name,email,pw ...
- module : 만든 스키마를 모듈화함
const mongoose = require('mongoose')
const userSchema = mongoose.Schema({
name: {
type:String,
maxlength:50
},
email: {
type: String,
trim: true,
unique:1
},
password: {
type:String,
minlength:5
},
lastname:{
type:String,
maxlength:50
},
role: {
type:Number,
default: 0
},
image :String,
token: {
type:String
},
tokenExp: {
type: Number
}
})
const User = mongoose.model('User', userSchema)
module.exports = {User}
<postman 이용>
- postman?
API 개발을 보다 빠르고 쉽게 구현 할 수 있도록 도와주며,
개발된 API를 테스트하여 문서화 또는 공유 할 수 있도록 도와 주는 플랫폼
쉽게말해서, url로 테스트하는데는 한계가 있으니까 편리하게 테스트하는데 필요한 인터페이스임!
https://www.postman.com/downloads/
Download Postman | Get Started for Free
Try Postman for free! Join 17 million developers who rely on Postman, the collaboration platform for API development. Create better APIs—faster.
www.postman.com
- body-parser dependency 설치
클라이언트(브라우저)가 보낸 자료들을 body-parser를 이용해서 서버에서 받음!
index.js
const express = require('express')
const app = express()
const port = 5000
const bodyParser =require('body-parser')
const { User } = require('./model/User')
/*body-parser 옵션*/
//application/x-www-form-urlencoded 처럼 생긴 데이터를 분석해서 가져올수 있게함
app.use(bodyParser.urlencoded({extended:true}))
//application/json 타입을 가져옴
app.use(bodyParser.json())
const mongoose = require('mongoose')
mongoose.connect('mongodb+srv://username:1234@boilerplate.abcmn.mongodb.net/myFirstDatabase?retryWrites=true&w=majority').then(() => console.log('MongoDB connected...'))
.catch(err => console.log(err))
//url 이용
app.get('/', (req,res) => res.send('Hello World'))
//postman 이용: register route - 회원가입을 위한 register route
app.post('/register', (req, res) => {
//회원가입할때 필요한 정보들을 client에서 가져오면
//그것들을 데이터 베이스에 넣어준다.
const user = new User(req.body)
user.save((err,userInfo) => {
if(err) return res.json({ success:false, err})
return res.status(200).json({
success: true
})
}) //정보들이 user모델에 저장
})
app.listen(port, () => console.log(`Example app listening on port ${port}`))
- db에 잘 넣어지면 json형식으로 true, 실패시 false가 postman에 출력됨
- postman에 임의로 회원가입 양식을 json형식으로 입력함
- /register : 앤드포인트
<보안 설정>
1. 비밀 설정 정보 관리
- dev.js , prod.js로 나누기
- 보통 서버개발 환경은 local - dev - staging - product 로 나뉨
local : 내 pc
dev : 개발자들이 local에서 만든 코드를 합쳐서 테스트 해보는 서버
staging : 실제 운영 환경과 동일하게 만들어 테스트
prod : 실제 서비스 운영 서버
- 조건문을 통해 내 mongodb 정보를 가리기
dev.js (개인정보)
- .gitignore에 dev.js를 추가하여 github에 올릴때 dev.js 파일은 올리지 못하게 함
module.exports = {
mongoURI:'mongodb+srv://username:dddd@boilerplate.abcmn.mongodb.net/myFirstDatabase?retryWrites=true&w=majority'
}
prod.js
module.exports = {
mongoURI: process.env.MONGO_URI
}
key.js
- production(배포) 상태하면 prod.js 실행할 것 (임의로 설정한 것임)
if(process.env.NODE_ENV === 'production') {
module.exports = require('./prod')
}else {
module.exports = require('./dev')
}
index.js (추가)
const config = require('./config/key')
const mongoose = require('mongoose')
mongoose.connect(config.mongoURI).then(() => console.log('MongoDB connected...'))
.catch(err => console.log(err))
2. bcrypt로 비밀번호 암호화
- 비밀번호를 암호화하여 db에 저장하자
해싱 vs 암호화
: 암호화는 복호화 가능한 양방향의 방향성, 해싱은 복호화가 불가능한 단방향의 방향성
: 따라서 비밀번호는 해싱을 이용해야 함
- 해싱에다가 salt 기법을 추가하여 보안을 더 견고하게 만들 수 있음
즉, 해싱값에 무작위 문자열을 더 추가한다(해싱에 소금치기...ㅎㅎ)
- bcrypt 를 이용하자
User.js(추가)
- index.js의 save()함수 (db에 저장하는 메서드) 를 수행하기 전에 먼저 수행하는 함수
- pre() : 성공적으로 실행되면 next()즉, save() 함수가 실행된다.
- 비밀번호가 바뀌었을 때만 암호화 함(isModified)
const bcrypt = require('bcrypt')
const saltRounds =10 //임의 추가할 문자열의 개수
//save()호출전에 시행 mongoose에서 가져온 메소드
userSchema.pre('save', function( next ){
var user = this;
if(user.isModified('password')){
//비밀번호를 암호화 시킨다.
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()
}
});
const User = mongoose.model('User', userSchema)
module.exports = {User}
index.js
app.post('/register', (req, res) => {
//회원가입할때 필요한 정보들을 client에서 가져오면
//그것들을 데이터 베이스에 넣어준다.
const user = new User(req.body)
user.save((err,userInfo) => {
if(err) return res.json({ success:false, err})
return res.status(200).json({
success: true
})
}) //정보들이 user모델에 저장
})
- db에 암호화 되어 저장된 모습
<로그인>
- 로그인후 토큰 생성
User.js
- 원래 상태의 비밀번호(암호화 전)와 암호화된 비밀번호를 비교하는 함수
- 토큰 생성
userSchema.methods.generateToken = function(cb) {
var user = this
//jsonwebtoken을 이용해서 토큰을 생성하기
var token = jwt.sign(user._id.toHexString(),'secretToken')
user.token = token
user.save(function(err,user) {
if(err) return cb(err)
cb(null, user)
})
}
userSchema.methods.comparePassword = function(plainPassword, callbackfunction) {
//plainpw 와 암호화된 비밀번호가 맞는 지 확인. plainpw를 암호화 해서 비교한다.
bcrypt.compare(plainPassword, this.password, function(err, isMatch){
if(err) return callbackfunction(err),
callbackfunction(null, isMatch)
})
}
index.js
- 로그인 성공시 토큰 생성
app.post('/login', (req, res) => {
//요청된 이메일을 데이터베이스에서 있는지 찾는다
User.findOne({ email: req.body.email }, (err, user) => {
if(!user){
return res.json({
loginSucess: false,
message :"제공된 이메일에 해당하는 유저가 없습니다."
})
}
//요청된 이메일이 데이터베이스에 있다면 비밀번호가 맞는 비밀번호 인지 확인
user.comparePassword(req.body.password , (err, isMatch) => {
if(!isMatch)
return res.json({loginSucess: false ,message: "비밀번호가 틀렸습니다."})
//비밀번호까지 맞다면 토큰을 생성하기
user.generateToken((err, user) => {
if(err) return res.status(400).send(err);
// 토큰을 저장한다. 어디에? 쿠키, 로컬스토리지
res.cookie("x_auth",user.token)
.status(200)
.json({ loginSucess:true, userId: user._id})
})
})
})
})
<Auth 생성>
- auth 란? 특정 권한을 가진 유저만 해당하는 페이지를 볼 수 있도록 제한하는 기능
ex) 관리자 신분을 가진 user만 관리자 페이지 볼 수 있음, 로그인한 유저만 해당 페이지 볼 수 있음
- client의 쿠키에 담겨져 있는 토큰을 복호화 한 후 server의 db에 담겨져 있는 토큰과 비교하여
일치하면 auth 인증이 완료됨
auth.js
const {User} = require('../model/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가 없으면 미들웨어에 갇힘
})
// 유저가 있으면 인증 ok
// 유저가 없으면 인증 no
}
module.exports = {auth}
User.js
- 토큰을 복호화 하는 method
userSchema.statics.findByToken = function(token, cb){
var user = this;
//user._is +' ' =token
//토큰을 decode한다.
jwt.verify(token,'secretToken', function(err, decoded) {
//유저 아이디를 이용해서 유저를 찾은 다음에
//클라이언트에서 가져온 token과 db에 보관된 토큰이 일치하는지 확인
user.findOne({"_id": decoded, "token": token}, function(err,user){
if(err) return cb(err);
cb(null, user)
})
})
}
index.js
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
})
})
<로그아웃>
- 로그아웃 하려는 유저를 찾아서 server의 db에서 해당 유저의 토큰을 삭제함
index.js
- mongoose의 함수를 이용함(findoneandupdate)
- token을 삭제함
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
})
})
})