티스토리 뷰

TIL

토큰 만료처리하기

윤미주 2024. 7. 6. 01:30

프로젝트를 진행하면서 부족한 부분들이 생긴다.

그래서 프로젝트를 하면서 중간중간 리펙토링도 많고 추가 해야하는 기능도 많아 두서없는 TIL을 쓰게 된다.. ㅎ

쨋든.

 

📖AxiosInstance 이외에 토큰 만료처리가 필요한 이유.

API를 요청하는 특정 페이지로 이동하기 전에 refreshToken이 만료되었는지 확인이 필요했다. 

 

 

❓refreshToken이 만료되었는지 확인하는 이유.

백엔드로부터 ` accessToken `을 받아 localstorage에 저장해 사용하고

토큰을 디코딩하여 추가로 localstorege에 user로 저장하여 로그인 상태유지, 접근 제어 등에 사용하고 있다.

⬇️localstorage에 저장한 이유

더보기
더보기
더보기

cookie의 특성상 api 요청을 보낼 때마다 헤더에 담겨 함께 전송된다.

백앤드에게 요청할때 accessToken이 필요없음에도 무의미하게 보내게 되면 오버헤드가 발생된다.

때문에 localstorage를 선택하게 되었다.  

 

백앤드는 로그인 요청을 보내면 응답값으로 accesstoken을 보냄과 동시에 헤더에 cookie를 담아 보냄.

 

이때 문제가 refreshToken이 만료되면 로그인을 다시 필요로 하는데,

api 요청을 해 refreshToken 만료여부를 확인하기 전까지는 계속 로그인을 유지하는것이 문제가 되었다. 

특히 글 등록페이지에서 api요청이 필요한 곳들이 있는데 그때가서 로그인페이지로 리다이렉션..

 

그래서 localstorage에 담은 값을 신선하게 해줄 타이밍이 필요했다.

 

⭐ 초기에 해결 방안으로 생각한 setTimeout

사용자가 로그인했을때의 시간을 타임스탬프로 남겨 setTimeout을 이용해 24시간 후에 localStorage에서 삭제할까 싶었으나  문제점이 많았다. 

 

🔥문제점

1. 브라우저 창을 닫게되면 setTimeout은 작동을 멈춤.

2. 사용자가 여러개의 탭을 열어두는 경우 중복된 타이머 발생. (백엔드가 중복 로그인 방지해두어 무시가능)

3. 브라우저가 비활성화 상태일 경우 타이머의 정확도 떨어짐.

4. 백앤드가 refreshToken 만료시간을 정해두었음에도 불구하고 프론트에서 시간을 임의로 정하여 로그아웃 시키고 재로그인을 하게 하는 것은 전혀 불필요한 행동... 이라고 나만의 생각.. 

 

 

💡해결방법

accesstoken을 디코딩해보면 토큰 발급시간과 만료시간을 함께 담아 보내주었다.

나는 디코딩한 값을 user라는 key로 localstorage에 저장

그래서 만료시간을 활용해 `timeStamp` 와  `expirationTime`이라는 유틸리티 함수를 만들었다. 

` expirationTime `은 api 요청이 필요한 페이지로 리다이렉션 되는 버튼에서 호출해주면 된다.

timeStamp의 역할
1. 토큰을 디코딩하여 담아둔 값 중 만료시간 불러와 밀리초 단위로 변환
2. 만료시간과 현재시간을 비교
3. 만료시간이 현재시간보다 크다면 false를 반환 그렇지 않은 경우는 true를 반환
expirationTime의 역할
1. timeStamp 호출하여 true인지 false인지 확인
2. true인 경우 accessToken 재발급 요청 

 

토큰 만료를 확인하고 필요 시 accessToken 요청. 

accessToken 요청 시 refreshToken을 함께 보내는데 refreshToken이 만료된 경우 로그인 페이지로 리다이렉션

 

 

👩‍💻코드 살펴보기

< timeStamp >

1. localsotrage에 key중 user 호출

const getLoginUserInfo = localStorage.getItem("user");

 

 

2. 호출된 값 JSON 형식으로 파싱

const userObj = JSON.parse(getLoginUserInfo);

 

 

3. 만료시간을 초단위에서 밀리초 단위로 변환

    const expValue = userObj.state.user.exp * 1000;

❓ Date.getTime() 메서드는 현재 시간을 밀리초 단위의 타임스탬프로 반환하고,

      exp는 초단위로 저장. 두값을 비교하기 위해 초단위를 일치시켜주어야함. 만료시간 key가 exp 

 

 

4. 현재 시간 구하기

const nowTime = new Date().getTime();

 

 

5. 현재시간과 만료시간을 비교해 현재시간이 큰 경우 true 반환

return nowTime > expValue;

 

 

6. 현재시간과 만료시간을 비교해 만료시간이 더 큰 경우 false 반환

  return false;

 

 

< timeStamp 전체코드 >

const timeStamp = () => {
  const getLoginUserInfo = localStorage.getItem("user");
  if (getLoginUserInfo) {
    const userObj = JSON.parse(getLoginUserInfo);
    const expValue = userObj.state.user.exp * 1000;

    const nowTime = new Date().getTime();
    return nowTime > expValue;
  }

  return false;
};

 

 

 

< expirationTime 전체코드 >

export const expirationTime = async () => {
  if (timeStamp()) {
    await getNewAccessToken();
  }
};

` timeStamp ` 호출해 true인 경우 accessToken 재발행을 도와줄 getNewAccessToken 호출!!!

 

 

🍀사용방법

  import { expirationTime } from "@/utils/timeStamp";
  
  const handleMenuClick = async (menu: MenuType) => {
    if (menu.signOut) {
      router.replace(menu.url);
      logOutMutate();
      clearUser();
    } else {
      if (menu.id === 4) {
        await expirationTime();
      }
      router.replace(menu.url);
    }
    setShowMenu(false);
  };

 

메뉴를 드롭다운할때 사용하는 함수인데, menu.id === 4 인 경우 글등록페이지로 리다이렉션 된다.

글 등록페이지는 api 요청이 아주 많은 곳이다. 

 

때문에 글 등록페이지로 리다이렉션 되기 전에 expirationTime 를 호출!! 

 

` expirationTime `를 호출했을때 예상 작동

1. timeStamp가 false인 경우에는 바로 글 등록페이지로 리다이렉션  

2. accessToken 만료 시 accessToken 요청.

3. refreshToken이 만료되지 않았다면 accessToken 재발행 후 글 등록페이지로 리다이렉션

4. refreshToken 만료 시 로그인페이지로 리다이렉션. 

 

 

⬇️getNewAccessToken 코드가 궁금하신 분들은 아래 블로그를 확인해주세요😃

https://anywhereim.tistory.com/112