웹/Node.js & React.js

[Node.js] postman을 이용하여 회원가입 기능 구현하기

팽팽 2022. 2. 12. 01:00

- 명령 프롬프트 창에 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 User = mongoose.model('User', userSchema)
module.exports = {User}

 

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
            })
        })
})