본문 바로가기
리액트 공부와 함께 하는 일상/7주차

[TIL] 1. RefreshToken

by fefe94 2022. 2. 24.

 

 

 

일정 시간이 지나면 만료되는 accessToken을
해결해줄 RefreshToken

 

Weekly Goals

  1. refrechToken! 왜 이렇게 늦게 나타났지! - RefreshToken
  2. Async-await를 for문에서 쓰면 안된다고?! - Promise.all
  3. 자꾸 새로 만들지 말고, 메모해 놓는거 어때? - Memoization
  4. 내 사이트 모바일에서도 보고 싶어! - @Media
  5. 그거 알아?! Graphql 은 사실 Rest-API  야! - Graphql

 

 


RefreshToken

과거 로그인 프로세스

 일정 시간이 지나면 만료되는 accessToken을 해결해줄 방법으로 RefreshToken이 있습니다. 이를 위해 로그인 프로세스를 다시 한번 짚고 넘어가겠습니다.

https://fe-dev94.tistory.com/56?category=955164 

 

[TIL] 1. 로그인의 역사

로그인의 필요성 브라우저, 프런트, 백엔드, 디비가 있습니다. (각각 포트가 필요해서 서버라고 부릅니다.) 브라우저 주소창에 주소를 입력하고 엔터를 누르면 프런트에서 html, css, js 같이 하드

fe-dev94.tistory.com

자세한 내용은 위 게시글을 참고해주세요.

 

백엔드 서버를 여러개

 

Browser에서 로그인 확인이 필요한 데이터 요청을 Backend로 보낼 경우,

 

예전 방식으로는 백엔드의 메모리세션에 로그인 정보들을 기록 했습니다.

하지만 이 방식은 메모리가 다 차게 될 경우 더 이상 정보 저장을 할 수 없 습니다.

이를 해결 하기 위해 같은 정보를 가진

백엔드 서버를 여러 개 만들어 처 했습니다.

 

  • Scale-up : Server가 더 빠르게 동작하기 위해 하드웨어 성능을 올리는 방법.
                  ( 백엔드 컴퓨터의 cpu를 늘려주고 메모리를 업그레이드 해줍니다.)
  • Scale-out : 하나의 Server 보다는 여러 대의 Server가 나눠서 일을 하는 방식.
                  ( 똑같은 백엔드 컴퓨터를 복사하기 붙여넣기 하는 식으로 수평적 확장을 해줍니다. )

 

그런데 정보는 같지만 처리는 다르기 때문에

특정 로그인 기록이 남아있는 백엔드 서버가 아니라면

특정 유저의 로그인 유무를 인지하지 못한다는 단점이 있었습니다.

(다른 백엔드에서 처리가 안되고 있는 이상태를

stateful(백엔드가 각각 자기만의 상태를 가지고 있는것.)하다고 합니다.)

 

이에 대한 대안으로 로그인 정보를

DB에 넣는 방식을 사용했습니다.

(백엔드는 이렇게 stateless가 됩니다)

(db에 로그인 정보 저장하기 방식을 사용하여

scale out이 가능하게 됩니다.)

 

이렇게 결국 어떤 컴퓨터로 api를 요청해도 상관없어졌습니다.

 

 

LB

 

 

참고로 이때..

LB 로드 밸런스가

가장 접속이 적은 컴퓨터를 선택해서

철수는 저리로 가

맹구는 이리로 가 

이런 식으로 보내줍니다. (← 배포 때 다시 공부 예정)

 

로그인 정보를 DB에 넣는 방식

 

하지만 이 방식도 문제점을 전부 해결하진 못했습니다.

데이터베이스에서 병목현상 ( 병의 목 부분처럼 넓은 길이

갑자기 좁아짐으로써 대표적으로 교통 정체 현상,

컴퓨터 성능 저하 현상 을 예로 들 수 있습니다. )

이 일어나게 되었기 때문입니다.

 

 

그렇다고 백엔드 서버와 같이 DB를

여러 개 복사하기는 쉽지 않았습니다.

 

그러나

결국에 로그인 과정 데이터가 디비에 있다고 하면

모두가 다 결국은 디비로 갑니다.

디비 트래픽에 몰려버리는 거죠.

 

아무리 백엔드 스케일 아웃 하면 뭐할까요!

디비의 스케일 사이즈가 안되는데!

백엔드 해결 되어 봤자 디비에 몰리면 무의미해지는 부분입니다.

 

그래서

디비도 나누기 시작합니다.

테이블을 쪼개주었습니다!

(파티셔닝, 디비 셔닝)

 

 

인증 테이블 파티셔닝

 

 

 

 

로그인 정보가 들어간 테이블을 쪼갠 것

인증 테이블 파티셔닝 이였는데,

정보를 나눠 보관하는 방식 을 사용했습니다.

(Ex. 1번부터 3000번까지는 1번 DB에 저장…)

위의 문제점들을 해결한 방식이였지만,

위 방식도 결국 DB에 접근해서

데이터를 꺼내와야하는 번거로움 이 있었습니다.

 

직접 디비(MySQL, 그래프큐엘)에 접근하는 것보다

변수에 저장하는 것이 저장 및 조회 속도가 훨씬 빠릅니다.

그래서 변수 형태로 데이터를 저장하는

데이터 베이스인 redis 에 데이터를 저장해줍니다.

(MySQL, 그래프큐엘 말고)

대신 컴퓨터를 껐다 켜면 데이터가 날아갑니다.

데이터가 날아가지만 속도가 빠르다는 것이 특징입니다.

최근에 많이 사용되는 방식입니다.

 

근데 Redis 자체에도 가지 않는 방식은 없는 걸까요?

디비 자체를 긁지 않아도 되는 법이요!

이 토큰을 만들 때

uuid로 만들기보다는

로그인정보를 객체 형태인 문자열로 바꾸고

암호화해서 그 결과를 토큰으로 사용하는 것은 어떨까요?

그게 바로 jwt입니다.

 

암호화된 로그인 정보를 담은 객체를 복호화하면

백엔드에서 복호화해서 바로 객체 내용을 꺼내볼 수 있습니다.

 

현재 로그인 방식 : JWT 

 

앞서 알아본 흐름들과 대안들의 최종점으로 나온 것이 JWT입니다.
JWT는 토큰 자체에 로그인 정보가 들어있었기에

백엔드에서 DB로 갈 필요가 없어졌습니다!

 

 

JWT : DB 검증이 불가능하다!

 

하지만 JWT도 백엔드에 들어가 접속의 유무만 구분하기에

중간에 탈취가 되어도 이 정보가 맞는지

DB검증이 불가하다는 한계 가 있습니다.

 

 

refreshToken : 만료 시간이 지나면 재발급

 

그렇기에 만료시간을 주어 시간이 지나면

재발급(refreshToken)시키게 되었습니다.

 

 

accessToken ( state에 담기 ) , refreshToken ( cookie에 담기 )

 

로그인을 진행했을 때 2개의 결과물을 보내주었습니다.

accessToken과 refreshToken 인데요?

받아온 accessToken을 state에 담아주고 refreshToken은 cookie에 담아줍니다.

 

accessToken - state 

refreshToken - cookie 

 

토큰이 만료가 된 경우

 

로그인 유무가 필요한 특정 API를 요청한다고 가정해봅시다.

accessToken을 함께 넘겨 주어 인가 를 받게 되겠죠.

(accessToken이 만료되지않았다면 잘 작동 할겁니다.)

 

하지만 토큰이 만료가 된 경우

만료된 토큰을 가지고 요청을 보냈을 경우에는

Browser에게 UNAUTHENTED 에러를 반환합니다.

 

이 에러를 받게 되면 Browser단에서는

app.tsx부분에서 받은 에러가 어떤 에러인지 체크한 후

 

인가 관련 에러일 경우

refreshToken을 가지고

accessToken 재발급 요청을 하게 되었죠.

 

그렇게 새로 발급받은 accessToken을 state에 다시 담아준 후

기존에 요청에 실패했던 API를

새 accessToken을 가지고 재요청하는 것입니다!

 

이 전체적인 흐름의 이해가 중요합니다.

 

 

 

 

app.tsx에 몇 가지 설정

 

uploadLink부분 수정

 

  1. 데이터 암호화를 사용하기위
    기존의 http uri를 https로 변경
  2. 쿠키를 포함시켜 보내기 위해
    credentialsinclude 변경
  3. onError() 를 사용해 errorLink 를 만듭니다.
    이 onError()안에는 콜백함수가 들어갑니다.
    콜백함수의 인자로는
    graphQLErrors, operation, forward 가 들어갑니다.

 

토큰 만료 에러(UNAUTHENTICATED)

 

만약 gql에러가 있다면...

에러를 하나씩 뽑아 해당 에러가 토큰 만료 에러(UNAUTHENTICATED)인지 확인해주고

토큰 만료 에러라면...

토큰 만료 에러라면

refreshToken으로 accessToken을 재발급 시켜주어야 합니다만
이 부분에서 ApolloClient세팅이 끝나지 않은 시점이기에
restoreAccessToken(useMutation)요청이 불가능합니다!


그렇기 때문에 graphql-request라이브러리를 사용하여 요청합니다.

 

재발급 받은 accessToken을 setAccessToken()을 통해 저장시켜주고,

방금 실패한 쿼리의 정보가 담긴 operation의 설정을

operation.setContext({})를 활용해,
accessToken만 변경
하여

forword(operation)로 재요청 의 내용이 들어갔고,

위 에러링크를 client(ApolloClient)에 연결 시켜줍니다.

  1. useEffect에서 localStorage에 저장시키던 부분도
    getAccessToken().then()를 통해 손쉽게 해결할 수 있게 되었습니다.
  2. postMan으로 gql요청도 보내봤습니다. 이로써 알게 된 것이 있습니다.
    gql은 항상 POST 방식이고 endPoint가 하나인 restAPI 였습니다.
    또한 POST 과정에서 데이터를 넣어 보내
    underFetching문제를 해결하는 것입니다.
  3. 중요했던 포인트는 각각의 요청에는 에러코드를 따로 줄 수 있었지만,
    POST 전체에 대한 요청은 항상 200(성공)코드를 받게 된다는 점입니다.

 

 

 

댓글