테이브 연합프로젝트에서 로그인 방식으로 JWT인증을 사용하기로 하여, JWT 방식에 대해 배우고 중요한 것들에 대해 정리해보려 한다. 일단, JWT 인증에 대한 개념을 익히기 전, 인증과 인가 개념에 대해 참고하자.
JWT 기반 인증의 경우, 토큰 탈취의 위험성을 피하고자 Access Token, Refresh Token으로 이중으로 나누어 인증을 하는 방식을 많이 사용한다.
💡 Access Token은 접근에 관여하는 토큰, Refresh Token은 재발급에 관여하는 토큰의 역할로 사용되는 JWT
시스템 플로우에서 발생할 수 있는 각 시나리오에 따라 구현 과정을 정리하려 한다.
//로그인 요청 코드
const onSubmit = async (e) => {
const {username, password} = getValues();
try{
const res = await axios.post('/login',{
username: username,
password: password
});
//header jwt 토큰 정보 받아오기
let jwtToken = res.headers.get("Authorization");
let refreshToken = res.headers.get("refresh");
//쿠키에 jwtToken, refreshToken 저장
setCookie('jwtToken', jwtToken, {path: '/'});
setCookie('refreshToken', refreshToken, {path: '/'});
// jwt토큰 디코딩
let decodingInfo = DecodingInfo(jwtToken);
//userId 추출
let userId = decodingInfo.id;
//사용자 정보 API 요청
const response = await axios.get(`/users/${userId}?userId=${userId}`);
console.log(response.data);
//사용자 정보 리덕스 저장
dispatch(authUser({
userIdx: userId,
userProfile: response.data.image,
nickname: response.data.nickname,
}));
//메인화면 이동
navigate('/')
} catch (error) {
switch(error.response.data.message){
//401-Unauthorized: Password Invalid
case "Password Invalid":
return alert("비밀번호가 맞지 않습니다. 비밀번호를 다시 확인해주세요")
//401-Unauthorized: Username Invalid
case "Username Invalid":
return alert("존재하지 않는 아이디입니다. 아이디를 다시 확인해주세요")
}
}
};
(1) Access Token의 유효기간이 만료되지 않은 경우
(2) Access Token의 유효기간이 만료된 경우: Access Token 재발급
2-2-1. Refresh Token의 유효기간이 만료되지 않은 경우
2-2-2. Refresh Token의 유효 기간이 만료된 경우
여기서 access token이 만료되었을 경우, 공통으로 refresh token을 활용해 access token을 재발급 받아 다시 함수를 처리할 수 있도록 axios interceptor을 활용해 구현하였다.
// API 요청을 보낼 때마다 호출되는 인터셉터를 설정
axios.interceptors.response.use(
response => response,
async error => {
const originalRequest = error.config;
if (error.response.data.message === "JWT Token Expired" && !originalRequest._retry) {
// JWT 토큰이 만료되었다는 응답을 받으면, 토큰 새로고침을 시도
originalRequest._retry = true;
const refreshToken = getCookie('refreshToken');
if (refreshToken) {
originalRequest.headers['Refresh'] = `Bearer ${refreshToken}`;
try {
// 새 토큰으로 원래 요청을 다시 시도
const res = await axios(originalRequest);
const newJwtToken = res.headers['Authorization'];
const newRefreshToken = res.headers['Refresh'];
// 갱신된 JWT 토큰과 리프레시 토큰을 저장
setCookie('jwtToken', newJwtToken);
setCookie('refreshToken', newRefreshToken);
return res;
} catch (error) {
return Promise.reject(error);
}
} else {
alert('로그인 먼저 진행해주세요.')
navigate('/login');
}
}
return Promise.reject(error);
}
);