Skip to content

프론트 엔드의 JWT

HaeMin Yoon edited this page Aug 21, 2022 · 1 revision

jwt토큰은 쿠키 세션 처럼 인증방법의 한 종류이다

출처: https://www.bezkoder.com/jwt-json-web-token/

image jwt 인증방법

image cookie session 인증 방법

둘의 공통점은 서버에 어떤 값을 주고 원하는 데이터를 받는다는 점이다

기존의 쿠키 세션은 세션아이디를 통해서 서버에 저장되어있는 데이터를 불러오지만 jwt는 서버에서 유효한지만 체크하고 데이터를 넘겨준다

JWT 토큰

- 구조

jwt는 기본적으로 토큰안에 데이터가 들어있다. Header, Payload, Signature 3부분으로 이루어지는데 각각의 부분은 Base64Url로 인코딩되어 있어 디코드를 하여 데이터를 얻는다. ( 보통 인증할때 백엔드에서 유효하면 디코드 해서 데이터를 넘겨준다)

image

단 여기서 인코딩은 암호화가 아니기 때문에 민감한 정보는 절대 추가 되어서는 안된다.

- 만료시간

또한 토큰은 만료시간을 가지고 있다 최대한 짧게 가져가는게 보안상 좋다 보통 2시간으로 잡는다 만료시간이 넘은 토큰으로 인증할시에는 서버에서 승인을 하지않는다 토큰을 재발급 해야한다.

- JWT를 왜 사용할까?

기존의 쿠키 세션방식은 데이터를 서버에 저장하는것이기 때문에 사용자가 많아질수록 세션DB를 늘려야하고 세션 아이디를 찾기위해서 서버의 자원을 사용하였다 하지만 JWT를 사용하면 토큰 자체에 데이터가 들어가기 때문에 이 세션DB가 필요가 없어지고 받아온 토큰을 바로 디코드 하기만 하면 되기 때문에 서버의 부담을 줄여줄수 있다.

JWT사용법

기본적으로 쿠키 세션과 마찬가지로 로그인 (사용자 인증완료)후에 토큰을 받아와서 브라우저에 저장한다. 토큰에는 access token과 refresh token으로 나눠져 있다.

토큰 받기

Access Token

JWT 토큰은 header로 들어오게 된다 따라서 header에 있는 데이터를 직접 추출해서 브라우저에 저장해야한다

image

토큰이 발급이 된다면 다음과 같이 header에 authorization으로 access token이 넘어오게 된다.

이 값을 저장을 할것인데 선택지는 3개이다.

1. localStorage 저장

  • 장점: 간편함 csrf 취약점을 막아줌
  • 단점: xss에 취약함

2. cookie에 저장

  • 장점: xss를 막아줌
  • 단점: csrf에 취약함

3. 코드내 변수로 위치

  • 장점: 보안상 가장 강력 해커가 접근하기가 힘듬(xss, csrf 방어가능)
  • 단점: 페이지가 새로고침될때마다 access Token을 서버에서 발급해 주어야함

각각의 장단점이 있는데 우리 프로젝트 같은경우에는 access token을 localStorage에 저장을 하였다 하지만 보안적으로는 취약했고 이 취약점을 막아줌 + access token이 만료 될때 새로 토큰을 발급해 주기 위한 인증 장치로 refresh Token을 추가하였다

Refresh Token

access Token이 만료가 되었을시에 access Token을 재발급 받기위해 필요한 토큰이다.

로그인시에 access Token과 refresh Token을 발급 받는다. access token과 같이 유효기간도 존재하지만 훨씬 길다.

access Token과 마찬가지로 토큰을 저장할수 있는 선택지는 3개이다

1. localStorage 저장

  • 장점: 간편함 csrf 취약점을 막아줌
  • 단점: xss에 취약함

2. cookie에 저장

  • 장점: xss를 막아줌
  • 단점: csrf에 취약함

3. 코드내 변수로 위치 (불가능)

  • access token은 브라우저에 저장되어있고 refresh token은 휘발성이면 무슨 필요가 있을까?

사실상 access token이랑 장단점은 똑같다

토큰 저장에 대한 내생각

하나를 local에 두었으면 하나는 cookie에 httpOnly설정을 하고 넣는게 맞다고 생각한다. 탈취가 되려면 xss취약점과 csrf 취약점을 모두 뚫어야 사용이 가능하기 때문이다.

솔직히 보안을 생각하면 access token은 휘발성으로 관리하는게 좋다고 생각한다. 하지만 그렇게 되면 매번 refresh token으로 재발급을 받아야하고 이건 사실상 쿠키 세션방식이랑 동일해 진다. JWT의 이점을 해친다고 생각하기 때문에 둘다 브라우저에 저장하는 방식을 채택 하였다.

구현

// react-query 이용
const SignInGoogle = useMutation(postSignInWithGoogle, {
    onSuccess: (res) => {
      const jwtToken = res.headers.authorization;
      if (jwtToken) {
        localStorage.setItem('access_token', res.headers.authorization);
				// localStorage에 access_token 저장
        navigate(PAGE_PATH.HOME);
      } else {
        navigate(PAGE_PATH.SIGNUP, { state: res.data });
				// 로그인 정보가 없으면 토큰 값이 넘어오지 않음
      }
    },
  });

토큰 전송

토큰이 필요한 api에 http 헤더에 토큰을 붙여서 전송해야 한다.

image

단 그냥 전송해서는 안되고 앞에 Bearer 를 붙여서 넘겨야한다

코드구현

// axios의 interceptors를 사용해서 모든 요청마다 header에 토큰을 담기게 하였다.

api.interceptors.request.use(
  (config) => {
    const accessToken = localStorage.getItem('access_token');
// localStorage에서 토큰을 가져옴
    if (!accessToken) return config;
// 토큰이 없으면 header에 토큰을 담지 않고 요청 보냄

    config.headers = {
      'Content-type': 'application/json',
      authorization: `Bearer ${accessToken}`,
    };
// header에 access token추가

    return config;
  },

  (error) => {
    return Promise.reject(error);
  }
);

토큰 유효기간 만료

유효기간 만료시에 refresh token을 이용하여 전송해야 한다. 이때 access token과 refresh token을 모두 전송하고 서버에서 마지막으로 발급된 토큰이 맞는지 체크를 하고 access token을 재발급 해준다.

코드 구현

// axios의 interceptors를 사용해서 특정에러(토큰 유효기간 만료) 발생시 refresh token을 이용해서 access token을 재발급 받는다.
api.interceptors.response.use(
  (response) => {
    return response;
  },

  async (error) => {
    if (
      error?.response?.status === 401 &&
      error?.response?.data?.code === 'BSE002'
    ) {
// 토큰 유효기간 만료 에러가 발생하면
      const accessToken = localStorage.getItem('access_token');
      try {
        const originalRequest = error.config;
        const token = await axios({
          method: 'get',
          url: API_PATH.REISSUE,
          headers: {
            authorization: `Bearer ${accessToken}`,
          },
        });
// access token과 refresh token을 전송한다. 여기서는 refresh token이 쿠키이기 때문에 따로 설정해 줄것은 없다.
        if (token) {
          localStorage.setItem('access_token', token.headers.authorization);
          return api.request(originalRequest);
        }
// 토큰 발급이 성공적으로 되면 access token을 업데이트하고 기존 api요청을 다시 보낸다.
      } catch (error) {
        const navigate = useNavigate();
        alert('세션이 만료되었습니다. 다시 로그인해 주시기 바랍니다.');
        navigate(PAGE_PATH.LOGIN);
      }
// 만약 발급이 실패하였으면 refresh token도 만료가 되었음으로 로그인을 다시 해야한다.
    }

    return Promise.reject(error);
  }
);

내생각

JWT가 나는 매력적으로 보이지 않는다 서버에 부담을 줄이는 장점에 대해서는 부정할수는 없으나 토큰 자체에 데이터를 들고있다는 점으로 인한 보안 이슈와 서버에서 유저상태를 관리할수 없다는게 너무 치명적인것 같다. 보안을 위해 access token을 휘발성으로 들고다니면 보안 이슈는 사라지나 이게 과연 JWT가 추구하는 방향인지는 모르겠다.