로그인 시 헤더에 반환된 토큰을 기준으로 JWT 인증이 필요한 라우트에서 request.header에 authorization이 있는지 확인 후
검증한다.
토큰이 없을 경우, Bearer 형식이 아닌경우, 페이로드에 담긴 유저 정보가 없는 토큰일 경우에는 에러를 발생시킨다.
export default async function (req, res, next) {
try {
const { authorization } = req.headers; // 헤더에서 토큰 정보 추출
if (!authorization)
// 토큰이 없을 시 에러
throw new CustomErr("요청한 사용자의 토큰이 없습니다.", 404);
const [tokenType, token] = authorization.split(" "); // 토큰 형식과 데이터 분리
if (tokenType !== "Bearer")
// 베어러 타입이 아닐경우 에러
throw new CustomErr("토큰 타입이 Bearer 형식이 아닙니다.", 400);
const decodedToken = jwt.verify(token, SECRET_CODE); // 토큰 디코딩
const userId = decodedToken.userId; // 디코딩한 토큰의 페이로드 내 유저ID 삽입
const user = await prisma.users.findFirst({
where: { userId: userId },
});
// 해당하는 유저가 없을 시 에러
if (!user) throw new CustomErr("토큰 사용자가 존재하지 않습니다.", 404);
req.user = user; // request에 user 객체 설정
next(); // 다음 미들웨어로 전달
} catch (err) {
return res.status(400).json({ message: err.message });
}
}
특정 라우터에서 중복된 부분이 있어 미들웨어로 만들어 추가해주었다.
인증 미들웨어를 거친 후 user 객체와 캐릭터 번호로 character 테이블에서 조회를 하고 있을 경우 character 객체로 다음 미들웨어로 전달해주고 아니면 에러를 반환.
export default async function (req, _, next) {
try {
// 인증 미들웨어에서 생성한 user와 URL 매개변수에서 추출
const {
user: { userNo },
params: { characterNo },
} = req;
const character = await prisma.characters.findFirst({
where: { characterNo: +characterNo }, // characterNo 와 일치한 데이터 요청
});
if (!character)
throw new CustomErr("캐릭터가 정보가 존재하지 않습니다.", 404); // 캐릭터 객체가 없을 시 에러
if (character.userNo !== userNo)
throw new CustomErr("캐릭터 접근 권한이 없습니다.", 401); // 캐릭터 소유권자와 토큰 인증자가 다를 시 에러
req.character = character; // request에 캐릭터 객체 설정
next();
} catch (err) {
return res.status(400).json({ message: err.message });
}
}
커스텀 에러 클래스를 생성하여 에러를 throw할 때 에러 정보와
status code를 받아 처리하는 미들웨어를 만들었다.
try-catch 구문을 통해 에러 부분을 customErr로 throw 해주면, catch는 에러 미들웨어로 정보를 넘긴다.
에러 미들웨어에서는 해당 에러 내용을 처리한다.
에러로그 미들웨어는 에러가 생겼을 경우 로그를 터미널에 출력한다.
constructor(message, statusCode) {
super(message); // Error 클래스의 message 속성 설정
this.statusCode = statusCode; // 추가적인 statusCode 속성 설정
}
export default function (err, req, res, next) {
// statusCode 가 전달되지 않은 경우 지정 외 에러이므로 500 할당
const statusCode = err.statusCode || 500;
// 서버 에러 출력
console.error(err);
if (statusCode === 500)
res.status(500).json({ errorMessage: "서버 내부 에러가 발생했습니다." });
// 클라이언트에게 에러 메시지를 전달
res.status(statusCode).json({
errorMessage: err.message,
statusCode: statusCode,
});
}
// winston 설정에서 로그 레벨을 error로 설정
const logger = winston.createLogger({
level: "error", // error 로그만 출력
format: winston.format.json(),
transports: [
new winston.transports.Console({
level: "error",
}),
],
});
export default function (req, res, next) {
const start = new Date().getTime();
res.on("finish", () => {
// 응답속도 계산
const duration = new Date().getTime() - start;
if (res.statusCode >= 400) {
// 오류 상태 코드일 경우 error 레벨로 로그
logger.error(
`Method: ${req.method}, URL: ${req.url}, Status: ${res.statusCode}, Duration: ${duration}ms`
);
}
});
next();
}