Refresh Token 저장 위치

  • 크게는 두 경우로 저장할 수 있는데 Backend DB, Redis 등의 Storage에 저장하거나 Client측에 저장할 수 있다.
  • Backend에 저장할 경우 JWT의 원래 도입 배경인 서버를 Stateless하게 유지하려는 노력과 상반 될 수 있다.
  • Client측에서는 Local Storage 혹은 Cookie에 저장할 수 있다.

 

Front

토큰 기반 인증에서는 로그인한 사용자의 상태 정보를 클라이언트 단의 저장소(쿠키, 로컬 스토리지, 세션 스토리지 중 하나)에 토큰의 형태로 저장한다. 그렇다면 토큰은 쿠키, 로컬 스토리지, 세션 스토리지 중 어디에 저장하는 게 좋을까? 정답이 있는 것은 아니지만, 세션 스토리지에 저장하는 경우는 많지 않고 보통은 쿠키나 로컬 스토리지에 저장한다. 그리고 이 둘은 각각 장단점이 존재한다.

 

1. Local Storage

  • local storage 는 구현이 쉽지만 자바스크립트로 접근이 너무 쉬워서 XSS 공격에 취약하고 보안상 문제 소지가 많다.
  • 로컬 스토리지와 같은 웹 스토리지의 데이터는 매 요청마다 서버에게 자동으로 전송되는 것이 아니기 때문에 CSRF 공격에 상대적으로 안전하다.
  • 다만, HttpOnly 옵션으로 XSS 공격을 방지할 수 있는 쿠키와 달리 웹 스토리지는 그러한 보안 옵션들을 설정할 수 없다. 
  • 따라서 이스케이프 처리 등으로 XSS 공격에 대한 대응을 면밀히 해줄 필요가 있다. XSS 취약점이 발생하면 동일한 도메인 내에서 CSRF 공격까지 발생할 수도 있기 때문에 더욱 주의해야 한다.
  • 다행히 대부분의 현대 웹 어플리케이션이나 라이브러리들은 자동 이스케이프 처리를 해주는 경우가 많다. 하지만 어찌 됐든 한 번 설정해주면 그만인 쿠키에 비해서는 신경 써줄 부분이 많다는 단점이 있다.

 

  • HTTPOnly 와 Secure 옵션을 사용하고 CSRF 공격에 대비를 하면 어느정도 보안을 할 수 있다.
  • 클라이언트 단에 저장되는 만큼 토큰은 탈취되기 쉽다. 따라서 보안을 더욱 강화하는 것이 중요한데, 다행히도 쿠키는 보안을 위한 몇몇 옵션들을 설정하는 것이 가능하다.
    • 먼저, XSS 공격에 의해 쿠키가 탈취당할 수도 있으므로 HttpOnly 옵션을 설정하여 JavaScript로는 해당 쿠키에 접근할 수 없도록 해야 한다.
    • 다음으로, CSRF 공격에 의해 의도치 않게 쿠키가 전송되는 것을 막기 위해 SameSite 옵션도 Lax 혹은 Strict로 설정해줘야 한다.
    • 또한, Secure 옵션도 설정하여 HTTPS 프로토콜을 사용할 때만 쿠키가 전송될 수 있도록 해야 한다.

 

Back(Server Side)

서버 issue를 백엔드에서 관리하기 용이하다. 반대로 access token을 재발급하기 위한 API 요청이 많아질 수 있다.

 

1. Session

  • 세션에 저장하고 세션 만료 주기를 늘리는 방식. 사용자가 많은 경우를 고려하면 사용하지 않는 것이 좋다. 또한 JWT 이용 목적에 적합하지 않다.

2. DB

  • refresh token 를 데이터베이스에 저장한 후 index 값을 쿠키나 로컬스토리지에 저장하는 방법. 클라이언트 단에서 refresh token 을 노출하지 않는 추세로 바뀌는 중. refresh token을 DB에 저장해야만 탈취 당했을 때 해당 토큰을 폐기할 수 있다.(RTR기법)

 

내가 고려하고 있는 도입 방식

Refresh Token을 백엔드 서버에 저장

refresh token을 백엔드 서버에 저장해두고 클라이언트는 refresh token에 대한 정보를 쿠키로 가지고 있는 방식이다. 새로고침했을 때 access token을 html 자원과 함께 받기 위해 반드시 백엔드 서버를 거쳐야한다.

 

장점

  • 서버 issue를 백엔드 서버에서 대부분 처리하기 때문에 관리에 용이하다.
  • 사용자가 많아짐에 따라 생기는 서버 issue를 백엔드에서 모두 처리하기 때문에 관리 포인트가 프론트 서버, 백엔드 서버 둘로 나뉘지 않고 백엔드 서버 한 곳에서 처리된다. 따라서 서버 issue 관리에 용이하다.

단점

  • 새로 고침하면 항상 프론트 서버를 거쳐 백엔드 서버까지 요청이 전달된다.
  • token이 프론트 서버에 따로 저장되지 않기 때문에 access token을 얻기 위해서는 항상 백엔드 서버까지 요청이 전달되어야 한다. 요청 횟수, 요청에 드는 간접비용 등을 고려해봤을 때 비효율적일 수 있다.

 

Redis에 Refresh Token을 저장하는 이유

레디스는 key-value 쌍으로 데이터를 관리할 수 있는 데이터 스토리지이다. 데이터베이스라고 표현하지 않은 이유는 기본적으로 레디스는 in-memory로 데이터를 관리하므로, 저장된 데이터가 영속적이지 않기 때문이다.

데이터가 HDD나 SDD가 아니라 RAM에 저장하므로 데이터를 영구적으로 저장할 수 없는 대신, 굉장히 빠른 액세스 속도를 보장받을 수 있다. 빠른 액세스 속도와 휘발성이라는 특징으로 보통 캐시의 용도로 레디스를 사용한다. Refresh Token의 저장소로 레디스를 선택한 이유도 위와 같다. 빠른 액세스 속도로 사용자 로그인시 (리프레시 토큰 발급시) 병목이 되지 않는다.

 

또한 리프레시 토큰은 발급된 후 일정 시간 이후 만료되어야 한다. 리프레시 토큰을 RDB등에 저장하면, 스케줄러등을 사용하여 주기적으로 만료된 토큰을 만료 처리하거나 제거해야한다. 하지만, 레디스는 기본적으로 데이터의 유효기간(time to live)을 지정할 수 있다. 이런 특징들은 리프레시 토큰을 저장하기에 적합하다.

 

그리고 리프레시 토큰은 실수로 제거되어도 다른 데이터에 비해 덜 치명적이다. 최악의 경우가 모든 회원들이 로그아웃 되는 정도이다.

물론 JWT와 같은 클레임 기반 토큰을 사용하면 리프레시 토큰을 서버에 저장할 필요가 없다. 하지만, 사용자 강제 로그아웃 기능, 유저 차단, 토큰 탈취시 대응을 해야한다는 가정으로 서버에서 리프레시 토큰을 저장하도록 구현하는게 좋다.

 


[ 참고 자료 ]

+ Recent posts