우테코

Refresh Token 도입기

prefer2 2022. 10. 23. 17:18

기존의 내편의 로그인은 1시간이 지나면 무조건 로그아웃이 되고 있다. 만약에 롤링페이퍼를 작성하고 있다가 확인 버튼을 눌렀을 때 로그아웃이 된다면 사용자는 매우 당황스러울 것이다. 또한 일반 사용자에게 ‘토큰이 만료되었습니다’ 라는 메시지를 보여주는 것은 상당히 어색하고 당혹스러운 일이다. 사용성과 기술적 시도를 위해 refresh token 도입을 결정하였다.

 

Access Token을 길게 하면 안되나요?

access token은 서버에서 저장하지 않는 jwt 값이다. 만약 이 정보를 해커가 탈취한다면? 이 토큰 값만을 가지고 모든 일을 수행할 수 있게 된다. 거의 모든 요청마다 access token을 실어서 보내고 있기 때문에 이 값은 탈취되기 매우 쉽고 탈취되어도 이를 알 수 있는 방법이 없다.

 

Refresh Token

refresh token은 access token을 새로 발급(refresh) 받기 위해 사용하는 토큰이다. 이 토큰은 서버에 저장된다.

Refresh Token는 Access Token의 탈취여부를 확인하는데 도움이 된다.

 

 

Client에서는 Refresh Token을 어디다 저장할까?


refresh token에 대한 전략을 정하고 이제 이 값을 저장만 하면 되는 상황이다. accessToken과 함께 어디다 저장을 하는 것이 옳을지에 대해 생각해보자

Local Storage

javascript의 접근이 쉽기 때문에 XSS 공격에 취약하다

Cookie

HTTPOnly 와 Secure 옵션을 사용하고 CSRF 공격에 대비를 하면 어느정도 보안을 할 수 있다.

더보기

쿠키도 결국에 보안에 취약한거 아닌가 싶었다. 백엔드 팀원이 HTTPOnly 옵션을 주면 된다라고 해서 찾아보았다. javascript를 통한 cookie 접근을 막아준다고 한다(document.cookie) 덕분에 XSS 공격을 막아줄 수 있다.

클라이언트 단에서는 설정할 수 없는 값이고 백에서 쿠키를 사용할 때 설정할 수 있는 값이다.

https://developer.mozilla.org/ko/docs/Web/HTTP/Cookies

 

 

 

Refresh Token이 탈취된다면...?


그럼 refresh token이 탈취되면 어떡하지?

결론부터 말하자면 우선 내편에서는 이러한 일은 발생하지 않는다라고 가정하고 진행했다. 이를 위해서는 여러 로직이 필요로 하고 지금의 프로젝트에서는 오버엔지니어링이라고 판단했다.

다음과 같은 방법을 적용한다면 refresh token의 탈취 여부를 확인할 수 있다고 한다

  • 데이터베이스에 각 사용자에 1대1로 맵핑되는 Access Token, Refresh Token 쌍을 저장한다.
  • 정상적인 사용자는 기존의 Access Token으로 접근하며 서버측에서는 데이터베이스에 저장된 Access Token과 비교하여 검증한다.
  • 공격자는 탈취한 Refresh Token으로 새로 Access Token을 생성한다. 그리고 서버측에 전송하면 서버는 데이터베이스에 저장된 Access Token과 공격자에게 받은 Access Token이 다른 것을 확인한다.
  • 만약 데이터베이스에 저장된 토큰이 아직 만료되지 않은 경우, 즉 굳이 Access Token을 새로 생성할 이유가 없는 경우 서버는 Refresh Token이 탈취당했다고 가정하고 두 토큰을 모두 만료시킨다.
  • 이 경우 정상적인 사용자는 자신의 토큰도 만료됐으니 다시 로그인해야 한다. 하지만 공격자의 토큰 역시 만료됐기 때문에 공격자는 정상적인 사용자의 리소스에 접근할 수 없다.

 

악의적 사용자는 Access Token의 유효기간을 알지 못하기 때문에 Refresh Token으로 새 Access Token을 요청하면 이미 Access Token이 존재하여 탈취 여부를 확인하는 방법이다. 하지만 이도 오래 기달린 후에 요청하면 의미가 없지 않나 싶었다.

ip가 달라지면 경고주기. 사실 완벽하게 토큰이 탈취되었는지 확인하는 방법은 어렵다. 이렇게 기존의 ip와 다른 ip로 접속을 한다면 사용자에게 경고를 줘서 refresh token을 무효화할 수도 있다.

팀에서 나왔던 이야기는 마이페이지에 신고버튼을 만들어서 만약에 내가 작성하지 않는 롤링페이퍼나 메시지가 작성되어있다면 관리자에게 신고할 수 있도록 하는 방법이었다.

refresh token은 access token에 비해 요청에 실리는 경우가 적다. access token이 만료된 경우에만 요청에 함께 보내기 때문에 이 타이밍을 맞추는 것은 매우 어려운 일이다. 이것도 하나의 보안이 아닐까 싶다.

refresh token의 매력은 서버 단에서 무효화를 해줄 수 있는 것에 있지 않은가 싶다. 사용성을 위해서도 좋지만 서버단에서 탈취가 일어났음을 알고 이를 무효화 해줄 수 있는 것이 가장 큰 특징인 것 같다.

 

내편의 Refresh Token 전략


토큰 전략을 이야기하면서 매우 다양한 전략들이 있구나 알 수 있었다. 디바이스의 정보를 저장하는 것부터 해서 refresh token 갱신 여부까지 다양하게 고민해볼 수 있었다. 여러 다른 서비스들에서 사용하고 있는 방식들을 보며 전략을 세우게 되었다.

내편에서 결정한 refresh token 전략은 다음과 같다.

  • 기존 Access Token 만료 기간을 1시간에서 30분으로 줄인다.
  • Refresh Token 만료 기간은 7일로 한다.
  • 발급한 Refresh Token은 서버에 저장한다.
  • Refresh Token은 UUID이다.
  • Refresh Token을 사용해서 새 Access Token을 요청시 Refresh Token의 만료 기간이 2일 이하로 남았다면 만료 기간을 서버에서 7일로 갱신해준다. (자주 접속시 무한 자동 로그인 가능)
  • Refresh Token의 탈취에 대응하기 위해 특정 Refresh Token무효화 및 신고를 받은 사용자의 전체 Refresh Token 무효화 기능을 제공한다.

 

프론트에서는 어떻게?


  • 로그인 시 access token과 refresh token을 cookie에 저장한다
  • 로그아웃 시 refresh token 삭제 요청을 한다
  • access token이 사라져 401 응답이 올 시 refresh token을 사용하여 새로운 access token을 달라는 요청을 한다. 해킹의 위험이 존재하지 않는 커스텀 에러코드인 3011, 3012인 경우에만 새로운 access token을 요청한다
  • refresh token에 대한 커스텀 에러코드를 따로 명시한다. refresh token 에러의 경우 해킹의 우려가 있다고 판단되어 이 에러가 발생할 시 로그아웃한다

 

진행하며 어려웠던 점

내편 프로젝트에서는 api요청을 위해 react query를 사용중이다. GET 요청 중에 access token의 기간이 만료되었을 시 react query의 refetch를 사용하여 실패한 요청을 쉽게 재시도 할 수 있었다. 하지만 POST, PUT, DELETE와 같이 mutation을 하는 요청 중에 만료되는 경우에는 이를 재시도할 수 있는 좋은 방법을 찾지 못했다.

에러처리 로직들을 react query defaultOption의 onError에 handler를 정의하여 사용하고 있다. 기존에 mutation 관련 로직들은 따로 hook으로 분리해 놓았기 때문에 이 mutation 함수를 재사용하고 싶었다. 따로 onSuccess 로직을 찾기 어려웠고, 각각의 경우에 대한 분기문처리가 불필요하다고 생각되었다.

우선은 error를 통해 알아낸 api 요청과 body값을 통하여 재요청을 한 후(새로운 mutate 함수로) reload를 통해 눈속임을 하는 방식을 선택하였다. refetch와 같이 mutation도 key값으로 재요청을 할 수 없는지 또는 key값과 success시 일어날 일을 정리해서 관리해야하는지는 조금 더 생각해봐야할 것 같다. refresh token을 도입하며 어떻게 해야 react query를 더 잘 쓸 수 있을지에 대해 고민할 수 있었다.

반응형

'우테코' 카테고리의 다른 글

에러 처리 방식에 대한 고민  (0) 2022.10.30
프론트엔드 성능 최적화 - 2  (2) 2022.09.12
프론트엔드 성능 최적화 - 1  (0) 2022.09.06
내편 UI 개발기  (6) 2022.09.02
[Level 2] 미션 1: 페이먼츠 1, 2단계  (0) 2022.05.16