[BookShop 프로젝트] jwt 토큰 추가, jwt 예외처리, try...catch, throw

jwt 토큰 발행, 검증

authorization-demo.js

var jwt = require('jsonwebtoken');
const express = require('express');
const app = express()
const dotenv = require('dotenv')
const path = require('path')
dotenv.config({path: path.join(__dirname, '../.env')})

// 서버 세팅 : 포트 넘버 3000으로 셋팅 
app.listen(process.env.PORT, () => {
  console.log('Server is running on http://localhost:3000')
})

// GET + "/jwt" : 토큰 발행
app.get('/jwt', (req, res) => {
  var token = jwt.sign({foo : 'bar'},process.env.PRIVATE_KEY);

  res.cookie("jwt", token, {
    httpOnly : true // 자바스크립트로 쿠키에 접근하는 것을 막음 (보안 강화)
  })
  res.send('토큰 발행 완료!');
});

// GET + "/jwt/decoded" : 토큰 검증
app.get('/jwt/decoded', (req, res) => {
  let receivedJwt = req.headers["authorization"];
  console.log(receivedJwt);
  var decoded = jwt.verify(receivedJwt, process.env.PRIVATE_KEY);
  res.send(decoded);
});

 

/jwt에서 설정한 cookie를 복사해서 

/jwt/decoded에서 Authorization에 넣어서 보내면

내용, 발급시간 담긴 jwt받아온 것을 확인할 수 있습니다. 

 


jwt 오류 예외처리 

1️⃣ TokenExpiresdError

: 유효기간이 지난 토큰 = 만료된 토큰

2️⃣ JsonWebTokenError

: 문제가 있는 토큰

 

try...catch

수많은 (개발자가 예상하지 못한) 에러(실수, 사용자 입력을 잘못 한 것, 디비가 응답을 잘못 ..)을 처리하는 문법

try {
	// A 코드 실행
} catch (err) {
	// 에러 처리 
}

 

let string = '{ "num1":1';

try {
    let json = JSON.parse(string);
} catch (err) {
    console.log("err.name : " +err.name);
    console.log("err.message : " + err.message);
    console.log("err : " + err); 
}

>>

err.name : SyntaxError
err.message : Expected ',' or '}' after property value in JSON at position 10 (line 1 column 11)
err : SyntaxError: Expected ',' or '}' after property value in JSON at position 10 (line 1 column 11)

 

try 구문의 코드를 실행하다가 에러가 발생하면, try코드를 멈추고 -> catch로 err와 함께 바로 빠져나갑니다.

try 구문에서 어떤 에러가 발생해도, if문 분기처리 필요없이 알아서 catch에 잡힙니다.(ex. SyntacError, TypeError)

 

이 말은, 원래라면 서버가 종료되지만 try catch를 사용하면 서버 종료없이 계속됩니다!

에러 객체

자바스크립트가 고대 개발자들의 노고를 보고 내장 에러 객체를 만들어뒀습니다.

JWT라는 모듈에서 제공하는 에러 객체도 있고, 직접 만들어 쓸 수도 있습니다.

 

throw 연산자

엘러를 발생시키는 연산자

let error = new Error("대장 에러 객체");
let syntxError = new SyntaxError("구문 에러 객체");
let referenedEror = new ReferenceError("참조 에러 객체");

console.log(error.name); 
console.log(error.message);

console.log(syntxError.name);
console.log(syntxError.message);

console.log(referenedEror.name);
console.log(referenedEror.message);

>> 

Error
대장 에러 객체
SyntaxError
구문 에러 객체
ReferenceError
참조 에러 객체

 

그렇다면, 언제 에러를 만들어줘야될까?

let string = '{ "num1":1';

try {
    let json = JSON.parse(string);

    if(!json.name){
        console.log("이름이 없습니다.");
    } else {
        console.log(json.name); //>> undefined : js입장에선 에러가 아니지만, 우리 입장에선 에러 = 입력값이 잘못된 에러  
    }
    
    let name = json.name;
    console.log(name);

} catch (err) {
    console.log("err.name : " +err.name);
    console.log("err.message : " + err.message);
    console.log("err : " + err); 
}

여기서 !json.name이 없을 경우의 오류에는 if문으로 처리했기 때문에 오류가 발생하여도 다음 코드를 실행합니다. 

let string = '{ "num1":1';

try {
    let json = JSON.parse(string);

    if(!json.name){
        //console.log("이름이 없습니다.");
        throw new SyntaxError("입력 값에 이름이 없습니다.")
    } else {
        console.log(json.name); //>> undefined : js입장에선 에러가 아니지만, 우리 입장에선 에러 = 입력값이 잘못된 에러  
    }
    
    let name = json.name;
    console.log(name);

} catch (err) {
    console.log("err.name : " +err.name);
    console.log("err.message : " + err.message);
    console.log("err : " + err); 
}

if 안에 throw을 넣어주면, 오류를 발생시켜준 것이기 때문에 바로 catch문으로 감
>>

err.name : SyntaxError
err.message : Expected ',' or '}' after property value in JSON at position 10 (line 1 column 11)
err : SyntaxError: Expected ',' or '}' after property value in JSON at position 10 (line 1 column 11)
admin@kayoungs-MacBook-Pro Devcourse % 


BookShop에 적용해보자

좋아요 API

 

const conn = require('../mariadb'); //db 모듈
const {StatusCodes} = require('http-status-codes'); //status code 모듈

const addLike = (req, res) => {
    // 좋아요 추가
    const {id} = req.params; 
    const {user_id} = req.body; 

    let sql = "INSERT INTO likes(user_id, liked_book_id) VALUES (?, ?);";
    let values = [user_id, id];
    conn.query(sql, values, 
        (err, results) => {
            if(err) {
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end(); 
            }

            return res.status(StatusCodes.OK).json(results);
    })
}

const removeLike = (req, res) => {
    // 좋아요 삭제
    const {id} = req.params; 
    const {user_id} = req.body; 

    let sql = "DELETE FROM likes WHERE user_id = ? AND liked_book_id = ?";
    let values = [user_id, id];
    conn.query(sql, values, 
        (err, results) => {
            if(err) {
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end(); 
            }

            return res.status(StatusCodes.OK).json(results);
    })
}

module.exports = {
    addLike,
    removeLike
}

여기서 수정 ==>

const conn = require('../mariadb'); //db 모듈
const {StatusCodes} = require('http-status-codes'); //status code 모듈
const jwt = require('jsonwebtoken'); //jwt 모듈
const dotenv = require('dotenv'); //dotenv 모듈
dotenv.config(); //dotenv 설정

const addLike = (req, res) => {
    // 좋아요 추가
    const book_id = req.params.id; 
    //const {user_id} = req.body; 

    let authorization = ensureAuthorization(req);

    let sql = "INSERT INTO likes(user_id, liked_book_id) VALUES (?, ?);";
    let values = [authorization.id, book_id];
    conn.query(sql, values, 
        (err, results) => {
            if(err) {
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end(); 
            }

            return res.status(StatusCodes.OK).json(results);
    })
}

function ensureAuthorization(req) {
    let receivedJwt = req.headers["authorization"];
    console.log(receivedJwt);

    let decodedJwt = jwt.verify(receivedJwt, process.env.PRIVATE_KEY);
    console.log(decodedJwt);

    return decodedJwt;
}

const removeLike = (req, res) => {
    // 좋아요 삭제
    const book_id = req.params.id; 

    let authorization = ensureAuthorization(req);

    let sql = "DELETE FROM likes WHERE user_id = ? AND liked_book_id = ?";
    let values = [authorization.id, book_id];
    conn.query(sql, values, 
        (err, results) => {
            if(err) {
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end(); 
            }

            return res.status(StatusCodes.OK).json(results);
    })
}

module.exports = {
    addLike,
    removeLike
}

login 했을 때의 Cookie를 복사해 

like post때에 Headers에 넣어줍니다.

 

 

장바구니

 

TokenExpriedError 해결하기

기존의 방식으로는 token의

expiresIn : '1m'

이 만료가 되면 오류가 나고 아예 서버가 종료됩니다. 

그렇기 때문에 try catch 문을 넣어주려고 합니다. 

const conn = require('../mariadb'); //db 모듈
const {StatusCodes} = require('http-status-codes'); //status code 모듈
const dotenv = require('dotenv'); //dotenv 모듈
dotenv.config(); //dotenv 설정
const jwt = require('jsonwebtoken'); //jwt 모듈
// 장바구니 담기
const addToCart = (req, res) => {
    const {book_id, quantity} = req.body;

    let authorization = ensureAuthorization(req, res);
    
    let sql = "INSERT INTO cartItems(book_id, quantity, user_id) VALUES(?, ?, ?);";

    let values = [book_id, quantity, authorization.id];
    conn.query(sql, values, 
        (err, results) => {
            if(err) {
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end(); 
            }

            return res.status(StatusCodes.OK).json(results);
    })  
}

// 장바구니 아이템 목록 조회 
const getCartItems = (req, res) => {
    const {selected} = req.body; // selected = [1,3]

    let authorization = ensureAuthorization(req, res);

    if(authorization instanceof jwt.TokenExpiredError) {
        return res.status(StatusCodes.UNAUTHORIZED).json({
            "message" : "로그인 세션이 만료되었습니다. 다시 로그인 하세요."
        });
    } else {
        let sql = `SELECT cartItems.id, book_id, title, summary, quantity, price 
                        FROM cartItems LEFT JOIN books 
                        ON cartItems.book_id = books.id
                        WHERE user_id = ? AND cartItems.id IN (?);`;
        let values = [authorization.id, selected];
        conn.query(sql, values, 
            (err, results) => {
                if(err) {
                    console.log(err);
                    return res.status(StatusCodes.BAD_REQUEST).end(); 
                }

                return res.status(StatusCodes.OK).json(results);
        })    
    }


}

// 장바구니 아이템 삭제 
const removeCartItem = (req, res) => {
    const cartItemId = req.params.id;

    let sql = "DELETE FROM cartItems WHERE id = ?;";
    conn.query(sql, cartItemId, 
        (err, results) => {
            if(err) {
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end(); 
            }

            return res.status(StatusCodes.OK).json(results);
    })
}

function ensureAuthorization(req, res) {
    try {
        let receivedJwt = req.headers["authorization"];
        console.log(receivedJwt);

        let decodedJwt = jwt.verify(receivedJwt, process.env.PRIVATE_KEY);
        console.log(decodedJwt);

        return decodedJwt;
    } catch (err) {
        console.log(err.name);
        console.log(err.message);

        return err;
    }

}

module.exports = {
    addToCart,
    getCartItems,
    removeCartItem
}

                              

JsonWebTokenError 해결하기

올바르지 않은 토큰이 들어왔을 때의 오류

else if(authorization instanceof jwt.JsonWebTokenError) {
        return res.status(StatusCodes.BAD_REQUEST).json({
            "message" : "잘못된 토큰입니다."
        });
    }