지난 시간에는 비동기를 활용해 주문 API를 완성했고, 이번 시간에는 JWT와 Request의 Headers를 활용해서 API 전반에 JWT를 적용하는 방향으로 코드를 수정해 볼게요.
추가적으로 try...catch 를 활용한 예외 처리 방법도 함께 배워봤어요.
에러가 발생하면 에러 메시지가 나오며 어떤 에러인지, 어느 부분에서 발생한 것인지를 알려줘요. 이렇게 에러 원인을 파악할 수 있는 이유는 자바스크립트에 '에러 객체'가 존재하기 때문이에요.
EvalError() , RangeError() 등등 다양하게 존재해요.let error = new Error('에러 객체');
console.log(error.name); // Error
console.log(error.message); // 에러 객체
console.log(error.stack);
/*
Error: 에러 객체
at Object.<anonymous> (경로 생략)
...
*/
try...catch 는 try 블록 안에서 코드를 실행시키고, 예외 또는 에러가 발생한 경우 즉시 실행을 중지한 뒤 catch 블록에서 에러를 처리하는 구문이에요.
try {
// 실행할 코드
} catch (err) {
console.log(err); // 에러 발생 시 처리할 코드
}
기존에 사용하던 if-else 예외 처리와의 큰 차이점은 '실행 중인 코드가 중간에 멈추느냐'의 차이예요. if-else 의 경우 에러가 발생하더라도 코드가 계속 실행될 수 있는 반면, try...catch 는 즉시 코드를 멈추고 예외 처리를 진행한다는 점에서 훨씬 안전해요.
throw
throw 키워드는 개발자가 직접 예외를 발생시킬(던질) 수 있는 키워드예요. 예외가 던져지면 해당 함수의 실행은 중지되고 catch 블록으로 예외가 넘어가요. (catch 가 없다면 프로그램이 강제 종료돼요!)
try {
let obj = {
email: 'kim@mail.com'
}
if (!obj.name) {
throw new SyntaxError("객체에 이름이 없습니다.");
} else {
console.log(obj.name);
}
} catch (err) {
console.log(err.name); // SyntaxError
console.log(err.message); // 객체에 이름이 없습니다.
}
추가적으로
finally블록을 같이 사용하면, 에러 발생 여부와 상관없이 무조건 실행시켜야 하는 코드를 작성할 수 있어요.
try {
// 동작 코드
} catch (err) {
// 에러 처리 코드
} finally {
// 항상 동작하는 코드
}
JWT(JSON Web Token)를 다시 복습해 볼게요.
const jwt = require('jsonwebtoken');
// 토큰 생성 (Sign)
const token = jwt.sign({ name: "bar" }, process.env.PRIVATE_KEY);
console.log(token);
// 토큰 검증 및 디코딩 (Verify)
const decoded = jwt.verify(token, process.env.PRIVATE_KEY);
console.log(decoded); // { name: 'bar', iat: 172737848 }
jsonwebtoken 모듈에는 기본적으로 제공되는 내장 에러 객체들이 있어요. 이를 활용해 예외 처리를 깔끔하게 할 수 있어요.
로그인할 때 생성된 토큰을 쿠키 등에 저장하고, 프론트엔드에서 요청 시 Request Headers 안의 Authorization 에 담아서 보내준다고 가정해 볼게요.
백엔드에서는 받아온 요청에서 토큰을 꺼내 jwt.verify() 를 통해 올바른 사용자인지 확인하고 데이터를 사용해요.
장바구니 담기, 조회, 삭제 기능 등에서도 동일하게 JWT를 이용해 사용자 정보를 가져오도록 수정했고, 이번에는 앞서 배운 try...catch 를 사용해 예외 처리까지 추가해 주었어요.
// 컨트롤러 부분
const someController = (req, res) => {
// ...
const userId = decodeUserId(req, res);
// 발생한 에러 객체의 타입에 따라 알맞은 응답을 내려줘요.
if (userId instanceof jwt.TokenExpiredError) {
return res.status(StatusCodes.UNAUTHORIZED).json({
message: "로그인 세션이 만료되었어요.",
});
}
if (userId instanceof jwt.JsonWebTokenError) {
return res.status(StatusCodes.BAD_REQUEST).json({
message: "토큰이 이상해요. 다시 확인해 주세요.",
});
}
// ... 정상 로직 처리
}
// 토큰 디코딩 공통 함수
const decodeUserId = (req, res) => {
try {
const token = req.headers.authorization;
const decoded = jwt.verify(token, process.env.PRIVATE_KEY);
return decoded.userId;
} catch (err) {
console.log(err.name);
console.log(err.message);
// 에러가 발생하면 에러 객체 자체를 리턴해서 컨트롤러가 처리하게 해요.
return err;
}
};


헤더의 jwt로 인가를 구현한 캡쳐본입니다.