[0731] 브라우저 종료 시 JWT 제거

nikevapormax·2022년 7월 31일
2

TIL

목록 보기
85/116
post-custom-banner

iPark Project

보완 필요 부분

  • 내가 진행하고 있는 프로젝트에서 로그인은 JWT를 통해 하고 있다.
  • 프로젝트를 진행하던 도중 아래와 같은 문제가 발생했다.
- access token이 만료되면 사용자 정보가 바로 undefined가 된다.
- 브라우저를 사용하다 종료하면 JWT 관련 정보가 local storage에 전부 그대로 남아있다.
  (로그아웃을 하지 않고 껐을 경우)

refresh token을 활용한 access token 갱신

  • 지금까지 access token이 만료되면 refresh token을 활용해 갱신할 수 있다는 것은 알고 있었다. 하지만 활용은 하지 않았었다.
  • 기껏해야 시연 잠시하고 말고 발표 때 잠깐 보여주고 말고 하기도 했고, 만료 시간을 두둑하게 주면 프로젝트를 진행하면서도 전혀 문제가 없기 때문이었다.
  • 하지만 이번에는 실제 사용자들이 우리 프로젝트를 평가하게 되면서 access token 갱신의 필요성을 크게 느꼈다.
  • 거두절미하고 코드부터 보자.
// refresh token으로 access token 발급
function refreshToken() {
  const payload = JSON.parse(localStorage.getItem("payload"));

  // 아직 access 토큰의 인가 유효시간이 남은 경우
  if (payload.exp > (Date.now() / 1000)) {
    return;
  } else {
    // 인증 시간이 지났기 때문에 다시 refresh token으로 access token 다시 요청
    const requestRefreshToken = async (url) => {
      const response = await fetch(url, {
        headers: {
          "Content-Type": "application/json"
        },
        method: "POST",
        body: JSON.stringify({
          "refresh": localStorage.getItem("refresh")
        })
      }
      );
      return response.json();
    };

    // 다시 인증 받은 accessToken을 localStorage에 저장하자.
    requestRefreshToken(`${backendBaseUrl}user/api/token/refresh/`).then((data) => {
      // 새롭게 발급 받은 accessToken을 localStorage에 저장
      const accessToken = data.access;
      localStorage.setItem("access", accessToken);
    });
  }
};
refreshToken()
  • 해당 코드의 포인트는 payload의 exp 즉 만료시간을 활용하는 것이다.
  • 만료시간이 지나게 되면 사용자는 바로 undefined가 되기 때문에 만료 시간을 확인해가면서 사용자가 정의되지 않기 전에 갱신을 해주는 것이다.
  • 아직 완성본이 아니라 아래의 문제점이 존재한다.
    • 사용자가 사용하다가 갑자기 undefined가 된다.
    • 운좋게 페이지 이동이라도 있다면 전혀 사용자가 눈치 못챌 것이지만 그렇지 않다면 새로고침을 해야 한다.
    • 토큰이 갱신되면서 다시 본인의 정보를 바로 불러올 수 있지만, 이 과정을 자동화하고 싶다.

브라우저 종료 시 JWT 관련 값 전부 삭제

  • JWT를 사용하면서 발급받는 refresh, access, payload 값들은 전부 local storage에 저장되게 된다.
  • local storage
    • 브라우저 내에 존재하는 저장소로써, 웹 브라우저가 종료되면 사라지는 SessionStorage 와는 다르게 브라우저가 종료되어도 저장된 정보가 계속 남아있는 공간이다.
  • 즉, 사용자가 로그아웃을 시도해 refresh, access, payload 값들을 삭제하지 않고 끈다면 계속 이 값이 남아있게 된다.
  • 여기서 문제가 발생했다.
  • 바로 이어서 사용하면 별 문제가 없겠지만, 몇 시간 뒤 혹은 다음 날 등 access token의 만료시간이 지나고 사용하게 된다면 또다시 사용자는 undefined가 되어 버린다.
  • 물론 해당 문제는 로그인이 필요한 부분에서만 일어난다. 나의 경우 로그인이 필요한 부분에서 저런 메세지가 나온다면 로그인을 시도하겠지만(다른 서비스에서도), 아닌 사람도 있을테니 그냥 local starage를 브라우저가 꺼지면 아예 비워버리기로 했다.

첫 번째 시도

  • index.html은 우리 프로젝트의 메인 페이지이다. 테스트를 위해 해당 html 아래 쪽에 script를 만들어 아래와 같이 작성해 보았다.
<script>
    window.addEventListener("unload", deleteToken)
    function deleteToken() {
      localStorage.removeItem("payload")
      localStorage.removeItem("access")
      localStorage.removeItem("refresh")
    }
  </script>
  • unload를 통해 창이 닫히면 local storage를 완전히 비워주었다.
  • 맨 처음 index.html에 들어갔을 때 모든 값들이 삭제되어있었고, 이제 된 줄 알고 자려했다.
  • 그런데 혹시나 하는 마음에 로그인을 해야 사용 가능한 즐겨찾기 페이지로 들어갔다.
    • 역시나 undefined 였다.
    • 이유는 간단했다. index.html을 빠져 나가 다른 페이지로 이동한 것이기 때문에 당연히 local storage가 비워진 것이다.

두 번째 시도

  • 그렇다면 나에게 필요한 것은 프로젝트 내 페이지 간 이동에 대해서는 위의 함수가 발동되지 않아야 하고, 브라우저를 꺼서 나가버린다면 위의 함수가 발동되어야 하는 조건이었다.
  • 아래 블로그를 참고해 나에게 맞도록 수정을 조금 하였다. 감사합니다.
    Javascript Jquery 브라우저 종료시 로그아웃 처리하기 - beforeunload
  • 코드는 아래와 같다.
// 브라우저 종료 시 로그인한 유저의 토큰값 로컬 스토리지에서 삭제
// 유저가 window 사용 시에는 window가 닫힌 것이 아니다.
var closing_window = false;
$(window).on('focus', function () {
  closing_window = false;
});

$(window).on('blur', function () {
  closing_window = true;
  if (!document.hidden) { // window가 최소화된 것은 닫힌 것이 아니다.
    closing_window = false;
  }
  $(window).on('resize', function (e) { // window가 최대화된 것은 닫힌 것이 아니다.
    closing_window = false;
  });
  $(window).off('resize'); // multiple listening 회피
});

// 유저가 html을 나간다면 window가 닫힌 것으로 간주
$('html').on('mouseleave', function () {
  closing_window = true;
});

// 유저의 마우스가 window 안에 있다면 토큰들을 삭제하지 않음
$('html').on('mouseenter', function () {
  closing_window = false;
});

$(document).on('keydown', function (e) {
  if (e.keyCode == 91 || e.keyCode == 18) {
    closing_window = false; // 단축키 ALT+TAB (창 변경)
  }
  if (e.keyCode == 116 || (e.ctrlKey && e.keyCode == 82)) {
    closing_window = false; // 단축키 F5, CTRL+F5, CTRL+R (새로고침)
  }
});

// a 링크를 눌렀을 때 토큰값 삭제 방지
$(document).on("click", "a", function () {
  closing_window = false;
});

// 버튼이 다른 페이지로 redirect한다면 토큰값 삭제 방지
$(document).on("click", "button", function () {
  closing_window = false;
});

// submit이나 form 사용 시 토큰값 삭제 방지
$(document).on("submit", "form", function () {
  closing_window = false;
});

// toDoWhenClosing 함수를 통해 window가 닫히면 토큰 관련 값 전부 삭제
var toDoWhenClosing = function () {
  localStorage.removeItem("payload")
  localStorage.removeItem("access")
  localStorage.removeItem("refresh")
  return;
};

// unload(window가 닫히는 이벤트)가 감지되면 closing_window가 true가 되고 토큰 관련 값들 전부 삭제
window.addEventListener("unload", function (e) {
  if (closing_window) {
    toDoWhenClosing();
  }
});
  • 브라우저가 닫히는 데 정말 종료하시겠습니까? 라는 메세지는 현재 나에게 필요하지 않기 때문에 그대로 unload를 사용했다.
  • 중요한 포인트는 페이지 간 이동에 대해 조건을 다 달아주어 페이지를 위에 작성된 방법으로 이동하게 되면 local storage가 비워지지 않는다는 점이다.
profile
https://github.com/nikevapormax
post-custom-banner

0개의 댓글