☑️What I Learn
try-catch 문
코드 실행 중 발생할 수 있는 예외 상황을 처리할때 사용된다. 주로 오류가 발생할 가능성이 있는 코드 블록을 보호하고, 에외가 발생했을 때의 처리를 정의한다.
try {
let result = riskyFunction();
console.log(result);
} catch (error) {
console.log("예외가 발생했습니다:", error.message);
}
- 예외의 종류마다 처리하지 않고, 한번에 처리할 수 있다는점.
- 에러 발생 시 프로그램이 종료되지 않고, 바로 예외처리 catch 문으로 이동하는 점.
- 모든 분기를 try-catch로 처리하는 것은 비효율적이다
if 문과의 차이점
- if 문은 조건에 따라 코드 실행 흐름을 분기하기 위한 것이고, try-catch 문은 실행 중 발생할 수 있는 예외를 처리하기 위한 것이다.
- if 문은 조건을 확인하고 분기가 필요할때 사용하지만, try-catch 문은 예외가 발생할 가능성이 있는 코드 블록을 보호할때 사용한다.
- if 문은 조건이 참인지 거짓인지 판단해 코드블록을 실행하지만, try-catch문은 코드 블록을 실행하고, 예외가 발생하면 catch 블록에서 처리한다.
두 방법 모두 섞어서 사용하는 것이 좋다.
ReferenceError 객체
현재 범위에서 존재하지 않거나 초기화되지 않은 변수를 참조했을 때 발생하는 에러이다.
존재하지 않는 변수를 참조하려고 할때 발새앟는 표준 오류 객체이다.
`new ReferenceError()` 생성자를 이용해 객체를 생성한다.
에러가 발생했는데도, 서버가 죽지 않는다는 특징을 가진다.
Throw 문
사용자 정의 예외를 발생(throw)할 수 있다. 예외가 발생하면 현재 함수의 실행이 중지되고 (throw 이후의 명령문은 실행되지 않는다.), 제어 흐름은 콜스택의 첫 번째 catch 블록으로 전달된다. 호출자 함수 사이에 catch 블록이 없으면 프로그램이 종료된다. `에러 객체 실체화`
throw 문은 어떤 값이든 던질 수 있다. 일반적으로는 Error 객체 또는 Error 객체를 확장한 객체를 던지다.
주로 try-catch 로 분기한 예외처리에서, 특이점이나, 정의해주어야 하는 예외가 있는 경우 > try 구문 안에서 if 문으로 분기하여 사용하는데, 이때 에러메시지를 명확하게 던지는 역할로 사용한다.
function checkVariable(variable) {
if (typeof variable === "undefined") {
throw new ReferenceError("The variable is not defined");
}
return variable;
}
try {
checkVariable(someUndefinedVariable);
} catch (error) {
if (error instanceof ReferenceError) {
console.error("ReferenceError caught:", error.message);
} else {
console.error("Other error caught:", error.message);
}
}
pagination 페이징 기법, sql_calc_found_rows
전체상품개수 % LIMIT = 페이징 개수
현재 페이지 current page, 목록의 개수 total page 에 대한 정보가 필요하다!
limit offset 구조로 제한된 row의 개수를 가지고 온다. 이때 조회된 row의 전체 건수가 필요한데, count() 함스를 사용하면 데이터를 가져오는 시간보다, 타임아웃이 더 길게 걸릴 수 있다.
`SELECT sql_calc_found_rows ... FROM ... WHERE... LIMIT 10;`
`SELECT found_rows();`
select 에 `sql_calc_found_rows` 옵션을 붙여주면, 전체 row 수를 임시저장하고 (limit과 무관)
`found_rows()` 명령문에서 전체 쿼리에 대한 행 수를 반환한다.
- 장단점
DB의 INDEXING(인덱싱)이 잘 되어 있고 ORDER BY와 같은 정렬을 타지 않는다면 SQL_CALC_FOUND_ROWS를 사용하는 것이 좋을 수 있다.
그러나 인덱싱이 잘 되어 있고 Full Scan을 하지 않는 경우(정렬등을 하지 않을 때)에는 SELECT COUNT(1)과 같은 쿼리로 두 번 쿼리 수행이 더 빠를 수 있다.
Req.Header에 Authorization속성값으로 JWT 토큰값 전달
//GET + "/jwt" : 토큰 발행
app.get("/jwt", function (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", function (req, res) {
let receivedJwt = req.headers["authorization"];
var decoded = jwt.verify(receivedJwt, process.env.PRIVATE_KEY);
res.send(decoded);
});
token 변수에 토큰 생성해서 담고, `httpOnly cookie`로 생성된 토큰을 전달한다. httpOnly 옵션으로 스크립트에서 쿠키에 접근하지 못하도록 한다.
req 요청 헤더에서 authorization 헤더를 통해 전달된 JWT 를 가져온다. 유효시간이 만료되지 않은 토큰인지 `verify` 를 통해 검증한다.
Access Token, Refresh Token
단기 토큰 > 유효기간 만료 시 새로운 Access Token을 발급받기 위해 사용하는 장기 토큰이다.
Refresh Token은 클라이언트가 재인증 없이도 지속적으로 서비스를 위해 사용할 수 있다. 보통 몇 주에서 몇 달 정도 유효기간으로 설정한다.
auth.js
// Access Token 발급 함수
const generateAccessToken = (user) => {
return jwt.sign(user, process.env.ACCESS_TOKEN_SECRET, {
expiresIn: "15m",
issuer: "dah",
});
};
// Refresh Token 발급 함수
const generateRefreshToken = (user) => {
return jwt.sign(user, process.env.REFRESH_TOKEN_SECRET, {
expiresIn: "7d",
issuer: "dah",
});
};
const refreshToken = (req, res) => {
try {
let receivedJwt = req.headers["authorization"];
if (!receivedJwt) throw new ReferenceError("Refresh token must be provided");
receivedJwt = receivedJwt.split(" ")[1];
jwt.verify(receivedJwt, process.env.REFRESH_TOKEN_SECRET, (err, user) => {
if (err) throw new Error("Invalid refresh token");
const accessToken = generateAccessToken({ id: user.id, email: user.email });
const newRefreshToken = generateRefreshToken({ id: user.id });
res.cookie("accessToken", accessToken, { httpOnly: true });
res.cookie("refreshToken", newRefreshToken, { httpOnly: true });
res.json({ accessToken, refreshToken: newRefreshToken });
});
} catch (err) {
console.error(err.message);
res.status(401).json({ error: err.message });
}
};
로그인 시 쿠키에 accessToken과 refreshToken을 같이 담고, 만일 만료되었다면 > refresh토큰을 이용해 access Toekn을 갱신하는 코드
if (authorization instanceof jwt.TokenExpiredError) {
// Refresh Token으로 Access Token 갱신 시도
const refresh = await refreshToken(req, res);
if (refresh.error) {
return res
.status(StatusCodes.UNAUTHORIZED)
.json({ message: "로그인세션이 만료되었습니다. 다시 로그인하세요." });
} else {
authorization = refresh;
}
}
1. 요청을 보냈을때 ensureAuthorization 함수가 accessToken의 유효성을 검증한다.
2. 만료되었을경우 발생하는 에러 `tokenExpiredError`에서
3. refreshToken 함수가 refreshToken의 유효성을 검증한다.
4-1. refreshToken도 만료되어 에러가 발생할경우 > 만료 메시지 띄우고
4-2. 유효할 경우 refreshToken 함수에서 기존 refresh Token을 이용해서 새로 발급받은 accessToken을 `authorization = refresh;` 으로 저장한다.
# 참고
CORS 정책 - Cross Origin Resource Sharing
웹 애플리케이션이나 웹 사이트가 다른 출처(origin)의 리소스에 접근할 때 (SOP 브라우저 정책 위반) 발생하는 보안 메커니즘이다.
즉 프론트와 백의 프로토콜, 도메인, 포트번호 (==출처) 가 달라서 생기는 보안상에 에러이다.
이때 다른 출처의 자원 공유를 허용하는 브라우저 정책을 CORS 정책이라고 한다.
3가지 시나리오를 따라 정책을 설정하고, 프론트와 백의 헤더 인증을 규칙과 비교하며 유효한지 비교하는 방법으로 해결한다.
- Simple Request
- Preflight Request
- Credentialed Request
이외에도 클라이언트에서 프록시로 해결하는 방법이 존재한다.
faker 모듈로 더미 가짜 데이터 가져오기
☑️Liked, Learned, Lacked, Longer for
SQL 문에 대해서도 기본적인 것만 알고있었는데, 조금은 실전플젝에 적용할 수 있는 다양한 문법을 알게되어서 좋았다.
LIMIT, SQL_CALC_FOUND_FORS, FOUND_ROWS(), COUNT 차이, 인덱싱 등
고민해볼 것으로 access Token과 refresh Token을 분리해서 구현해보자고 했는데, 예전부터 해보고 싶었다. 강의에서는 다루지 않았지만, 스스로 찾아보고 구현해 보았다. 에러가 발생할 수 있지만, 로직을 이해할 수 있어서 좋았고, 프로젝트에 적용해보고 싶다.
좋은 백엔드 팀원을 만났어서 백에서 알아서 다 처리했다고, 받은 토큰값을 쿠키나 로컬호스트에 저장하고 있다가 요청이 들어올때 값을 가지고 같이 전달하면 된다는 설명을 토대로 구현을 했었다. 데브코스 풀스택 덕분에 그 알아서 다 처리했다는 로직에 대해 경험할 수 있어서 좋았다.ㅎㅎ
`httpOnly` 옵션이 보안에 적용된다는 사실도 알게 되었다.
마지막으로 항상 능력자들이 해결해주던 대망의 CORS. 그걸 이젠 내가 능력자가 되어서 처리해보는 경험?! 의 첫단계로 이전에 학습했던 내용(코데이토 대외비..ㅎㅎ)을 복습했다. 처음 들었을때는 백엔드의 지식이 부족했어서 암기였다면, 이제는 흐름이 머릿속에 그려지고 자연스럽게 이해가 된다! 이제 이거를 플젝에 적용할때 잘 할수 있겠지?!