본문 바로가기
Web/Node.js

[인프런] Node JS #8 ~ #14

by jionee 2020. 8. 10.
SMALL

#8 Nodemon 설치

Nodemon 이란 ? 

소스를 변경할 때 자동으로 감지해서 서버를 재시작해주는 툴

 

Nodemon 설치

npm install nodemon --save-dev

* -dev는 로컬에서만 개발할 때 사용하겠다는 의미

 

Nodemon 스크립트 추가

// package.jsaon

"scripts" {
"backend": "nodemon index.js",
}

 

$ npm run backend

backend 스크립트로 서버를 구동시키면 index.js가 Nodemon으로 열림 ( 코드 수정 후 서버 재시작 필요 X )

 

#9 비밀 설정 정보 관리 

비밀 설정 정보 가리기

소스코드 내에 포함된 비밀번호 등이 Git Hub에 공개되지않도록 하기

 

Local 환경 or Deploy(배포) 후

두 경우를 따로 생각해야함

development(Local) 모드인 경우 소스코드 내에서 변수를 가져갈 수 있음

production(Deploy) 모드인 경우 클라우드 사이트 내에서 변수를 관리해야함

 

config 폴더 생성

3개의 파일 만들어주기

 

key.js 작성

if(process.env.Node_ENV == 'production') { // 환경 변수
    module.exports = require('./prod') // production 모드면 prod.js 파일에서 가져오기
}
else {
    module.exports = require('./dev') // development 모드면 dev.js 파일에서 가져오기
}

 

dev.js 작성

module.exports = {
    mongoURI: 'mongodb+srv://jione:<password>@jione.2qflm.mongodb.net/test?retryWrites=true&w=majority'
} // index.js에서 가져오기

 

prod.js 작성

module.exports = {
    mongoURI: process.env.MONGO_URI // MONGO_URI 변수는 클라우드 사이트에서 설정
}

 

index.js 수정

const config = require('./config/key') // config 정의

mongoose.connect('config.mogoURI', { // config의 mongoURI
    useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, useFindAndModify: false
}).then(()=>console.log('MogoDB Connected...'))
.catch(err => console.log(err))

 

.gitignore 수정

dev.js

remote에 push 하면 dev.js 은 제외되어 올라감 ( 비밀번호 보호 )

 

#10 Bcrypt로 비밀번호 암호화 하기

Mongo DB의 collections 확인 해보기

비밀번호가 그대로 노출되어 DB에 저장됨 보안성 낮음

 

Bcrypt 라이브러리 다운로드

$ npm install bcrypt --save

 

비밀번호 암호화하기

// User.js에 추가

const bcrypt = require('bcrypt') 
const saltRounds = 10  // salt가 몇 글자 인지 설정

userSchema.pre('save',function( next ) {
    var user = this // userSchema를 가리킴

    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 // 암호화가 성공하면 password를 hash로 교체
                next()
            })
        })
    }
    else {
        next() // 비밀번호가 변경되지않으면 넘어감
    }
})

salt를 사용해 암호화

 

bcrypt를 이용한 비밀번호 암호화 테스트

비밀번호가 암호화된 hash값으로 저장됨

 

#11 로그인 기능 with Bcrypt (1)

Login Route 만들기

1.  데이터베이스에서 요청한 이메일 찾기

2. 데이터베이스에서 요청한 이메일이 있다면 비밀번호가 같은지 확인

3. 비밀번호까지 같다면 Token을 생성

 

 

이메일&비밀번호 확인

// index.js에 추가

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.js에 추가

userSchema.methods.comparePassword = function(plainPassword, cb) { // 비밀번호 비교 메소드
    // plainPassword 1234567 암호화된 비밀번호 $2b$10$HW5QnqP3SE6Ux85ezceHJeKrSphylPCYB587mmeGeTBgpkP3YWomy
    // plainPassword를 암호화해 비교해야함 복호화할 수 없음
    bcrypt.compare(plainPassword,this.password,function(err,isMatch){
        if(err) return cb(err), // 비밀번호가 같지않음
        cb(null,isMatch) // 비밀번호가 같음
    })
}

 

 

#12 토큰 생성 with jsonwebtoken

JSON WEB TOKEN 라이브러리 다운로드

$ npm install jsonwebtoken --save

 

Cookie Parser 다운로드

$ npm install cookie-parser --save

 

JWT 사용하기

https://www.npmjs.com/package/jsonwebtoken

 

jsonwebtoken

JSON Web Token implementation (symmetric and asymmetric)

www.npmjs.com

 

토큰 생성하기

//index.js에 추가

const cookieParser = require('cookie-parser')
app.use(cookieParser())


// user.comparePassword안에 추가 

    // 비밀번호까지 맞다면 토큰을 생성하기.
        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})

             })
//User.js에 추가

userSchema.methods.generateToken = function(cb) {
    
    var user = this

    // jsonwebtoken을 이용해서 token을 생성하기
    var token = jwt.sign(user._id.toHexString(), 'secretToken')
    // user._id + 'secretToken' = token
    // ->
    // 'secretToken' -> user._id

    user.token = token
    user.save(function(err,user){ // user DB에 토큰 저장 
        if(err) return cb(err)
        cb(null,user)
    })
    
}

 

#13 Auth 기능 만들기 

Authentication(인증)이 필요한 이유 ?

사이트의 페이지마다 로그인 여부, 유저의 권한에 따라 이용할 수 있는 범위가 다르기 때문

 

확인하는 방법 ?

전 강의에서 Token을 서버 사이드에서는 유저 정보 DB에 클라이언트 사이드에서는 Cookie에 저장했음

이 두가지 Token이 서로 맞는지 계속 체크

1. Cookie에 저장된 Token을 Server에 가져와서 복호화를 한다. 

2. 복호화를 하면 User ID가 나오는데 그 User ID를 이용해 DB User Collection에서 유저를 찾은 후 쿠키에서 받아온 Token을 User도 갖고 있는지 확인

3. 일치하면 인증이 완료 되고 해당하는 요청을 제공

 

Auth Route 만들기

루트 디렉토리에 middleware 폴더 생성 -> auth.js 파일 생성

// User.js에 추가

userSchema.statics.findByToken = function(token,cb){
    var user = this;

    //user._id + 'secretToken' = token
    // 토큰을 decode 한다.
    jwt.verify(token,'secretToken',function(err,decoded){
        // 유저 아이디를 이용해서 유저를 찾은 다음에
        // 클라이언트에서 가져온 token과 보관된 토큰이 일치하는지 확인

        user.findOne({"_id": decoded, "token": token},function(err,user){

            if(err) return cb(err)
            cb(null,user)
        })
    })
    
}

 

// 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() // 미들웨어에 갇히지않게 다음으로 넘김
    }) 

    // 유저가 있으면 인증 Okay

    // 유저가 없으면 인증 No !

}

module.exports = { auth };

 

// index.js에 추가

const {auth} = require('./middleware/auth')

app.get('/api/users/auth', auth , (req,res) =>{
  // 여기까지 미들웨어를 통과해 왔다는 얘기는 Authentication이 True라는 말
  req.status(200).json({
      _id: req.user._id,
      isAdmin: req.user.role === 0 ? false : true,
      isAuth: true,
      email: req.user.email,
      name: req.user.name,
      role: req.user.lastname
  })
})

 

#14 로그아웃 기능

로그아웃 Route 만들기로그아웃하려는 유저를 데이터베이스에서 찾아서 그 유저의 토큰을 지워준다.

* 토큰을 지워주는 이유 ? 잘못된 인증이 발생하지않도록

// index.js에 추가

app.get('/api/users/logout', auth, (req,res) => { // 만들어놓은 auth 메소드 사용

    User.findOneAndUpdate({_id: req.user.id}, // id를 찾아서
        {token: ""}, // token을 지워줌
        (err, user) => {
            if (err) return res.json({ success: false, err}) // 에러가 난 경우
            return res.status(200).send({ // 성공한 경우
                success: true
            })
        })
})

 

'Web > Node.js' 카테고리의 다른 글

[인프런] Node JS #1 ~ #7  (0) 2020.07.29

댓글