본문 바로가기
Spring Framework/스프링

(1) JWT Access, Refresh 토큰 + Redis : 소개 및 보안

by 스코리아 2023. 12. 25.

안녕하세요, 스코리아입니다.
오늘은 JWT(Access, Refresh 토큰)에 관해 자세히 소개해드릴 예정이며, Redis를 사용하였을 때의 장점과 Refresh 토큰에 관련된 보안 문제점 및 해결방안도 전해드리겠습니다. 다음 포스팅에는 스프링 코드와 함께 설명을 이어나가겠습니다.
 
 

세션 기반 인증 vs 토큰 기반 인증

사용자가 인증된 사용자인지 구분하기 위해서 대표적으로 세션 기반 인증, 토큰 기반 인증 방법이 존재합니다. 각각 장단점이 존재하므로 자신의 프로젝트의 상황에 맞게 선택하는 것이 좋겠습니다. 

세션 vs 토큰

 
보통 토큰 기반 인증 방법은 무상태성, 확장성, 무결성의 이유로 사용됩니다. 
- 무상태성 : 사용자의 인증 정보가 담겨 있는 토큰을 클라이언트에 저장하기 때문에 서버에 별도의 저장소가 없어, 완전한 무상태(stateless)를 가질 수 있습니다. 서버 확장 시 용이합니다.
- 무결성 : 발급 후 토큰의 정보를 임의로 변경할 수 없습니다. 토큰이 변조될 수 없습니다.
- 확장성 : 토큰 기반 인증을 사용하는 다른 시스템에 접근이 가능합니다 (Oauth 로그인)
 
세션 기반 인증 방법은 로그인 정보를 서버 측에서 관리하기 때문에 서버에 부하가 발생할 수 있습니다. 그리하여 REST API를 이용하여 CSR 방식의 백엔드 서버를 개발하기 위해서는 무상태성이 유지되는 토큰 인증 방식을 선택하는 것이 좋을 겁니다.
 
 

JWT란?

JWT는 Json Web Token의 약자로, JSON 객체를 이용하여 데이터를 주고받을 수 있도록 한 웹 토큰입니다. 인증이 필요한 API를 호출할 때, 무상태성을 유지하면서 요청과 응답에 토큰을 함께 보내어 사용자가 유효한 사용자인지 검사합니다. 이때 JWT 토큰이 전달됩니다.
이렇게 되면 토큰 하나로 어떠한 사용자인지 손쉽게 파악이 가능합니다.

JWT 구조

JWT는 header(헤더), payload(내용), signature(서명)의 구조를 가지고 있습니다.
 
- header(헤더)토큰의 타입과 signature를 Hashing 하기 위한 알고리즘 정보가 담겨 있습니다.
- payload(내용) : 실제로 사용될 데이터들이 담겨 있습니다. 정보의 한 덩어리클레임(claim)이라 부르는데, 클레임은 key-value 한 쌍으로 이루어져 있습니다. 등록된 클레임 중에는 iss(토큰 발급자 issuer), aud(토큰 대상자 audeince), sub(토큰 제목 subject), exp(토큰 만료시간 expiration), iat(토큰 발급 시간 issued at) 등이 있습니다.
- signature(서명) : 토큰의 유효성을 검증하기 위한 용도로 사용됩니다.
 
 

Refresh 토큰을 사용하는 이유

Access 토큰만을 사용한다고 가정해 봅시다. 만약 Access 토큰의 유효 기간이 짧다면, 금방 로그인이 풀려버려서 유저들이 불편해할 것입니다. 그렇다고 유효 기간을 길게 잡는다면 어떻게 될까요? 유효 기간이 길기 때문에 만료 기간 전에 Access 토큰이 탈취된다면, 탈취자는 손쉽게, 그리고 계속적으로 해당 유저의 아이디로 로그인이 가능할 것입니다.
 

JWT Access + Refresh Token Flow

 
그리하여 도입된 게 Refresh 토큰입니다. Refresh 토큰은 Access 토큰을 재발급하는 용도로만 사용되며, 다른 권한을 가지지 않습니다. Refresh 토큰은 대략 2주~6달 사이의 긴 유효기간을 갖는 반면에, Access 토큰30분 정도의 유효기간을 갖게 설정하여야 합니다. Access 토큰은 30분 정도로 유효기간이 짧으면, 탈취자가 토큰을 탈취해도 유효기간이 얼마 없기 때문에 금방 만료되어 보안이 크게 향상됩니다.
 
또한 유저들이 사이트를 방문하는 동안 로그인이 풀리지 않기 위해서는, Client Side에서 Access 토큰이 만료되기 3~5분 전에 Refresh 토큰을 이용해 'Access 토큰 재발급 API'를 호출하여 Access 토큰을 업데이트하면 되겠습니다.
 
즉, Access 토큰과 Refresh 토큰을 함께 사용함으로써 보안성, 성능, 편의성을 모두 맛볼 수 있습니다.
 
 

Redis를 사용하는 이유

 

Redis

 
Refresh 토큰을 DB에 저장하게 되면, '모든 기기에서 로그아웃', '강제 로그아웃', '토큰 차단'의 기능을 구현할 수 있으며, 이로 인해 보안을 크게 향상할 수 있습니다. 다만 Refresh 토큰을 RDB에 저장하게 된다면, 1초에 수천, 수만 명이 로그인을 했을 때 RDB의 과부하뿐만 아니라 엄청나게 많은 데이터가 누적되어 쌓일 것입니다. 데이터가 누적되지 않게 유효기간이 지난 것을 자동 삭제해 주는 스케쥴러를 만들어야 할 필요도 있습니다.
 
하지만, Redis를 사용하면 어떨까요? Redis는 NoSql로서, 인메모리 DB이기 때문에 Disk(하드)에서 불러오는 RDB보다 훨씬 속도가 빠릅니다. 속도가 빠르기 때문에 많은 사람이 로그인이 했을 때도 병목 현상이 생기지 않습니다. 또한 TTL(Time-To-Live)라 부르는 '유효기간' 설정도 가능하여, 유효기간이 지나면 자동 삭제되게 설정할 수 있습니다. 이러한 특징들로 볼 때 Redis를 사용하는 것이 매우 적합해 보입니다.
 
다만 Redis는 인메모리 DB이기 때문에 불가피한 상황으로 모든 데이터가 날아가는 경우가 생길 수는 있습니다. 하지만 Refresh 토큰 자체는 중요한 데이터라고 볼 수 없기 때문에 최악의 경우를 생각해 본다고 하더라도 모든 사용자가 로그아웃되는 상황만 생길 수 있습니다.
 
 

Refresh 토큰 문제 및 보안 강화 방법

최악의 경우, Refresh 토큰이 탈취당할 위험이 있습니다. Refresh 토큰을 탈취당하면, 탈취자는 해당 Refresh 토큰을 가지고 Access 토큰을 재발급받아 유저의 아이디로 로그인할 것입니다.
 
이를 막기 위해서는 첫 번째로 Access 토큰을 재발급해주기 전에, Access 토큰을 처음 발급한 사용자와 '아이피 주소'가 같은지, 'User-Agent' 헤더가 같은지 등을 비교하여 재발급 유무를 판단하는 방법이 있습니다.
 
'아이피 주소'로 비교하게 된다면, 스마트폰 사용자가 모바일 데이터를 사용하거나 와이파이를 변경하는 경우 로그인이 풀리는 일이 발생할 수 있으며 'User-Agent'로 비교하게 된다면, 탈취자는 Refresh 토큰을 탈취하면서 이미 침입 PC의 User-Agent를 파악할 가능성이 높기 때문에 User-Agent를 변형하여 API를 호출할 가능성이 있습니다.
그럼에도 불구하고 '아이피 주소'와 'User-Agent'를 비교하여 Access 토큰을 재발급해준다면, 보안의 기능은 크게 향상될 것입니다.
 
두 번째 방법은 Access 토큰을 재발급 시 기존 Refresh 토큰은 제거하고 새로운 Refresh 토큰으로 재발급해주시는 게 좋겠습니다. 또한 이 방식에서 Refresh 토큰 만료일의 3분의 2가 지났을 때만 Access 토큰과 Refresh 토큰을 재발급해준다면, Refresh 토큰의 만료일이 무분별하게 늘어나지 않아, 보안이 크게 향상될 것입니다.
 


지금까지 JWT가 무엇이고, Refresh 토큰을 사용해야 하는 이유, Redis를 활용하였을 때의 장점, Refresh 토큰의 보안 문제와 해결방안에 대해서 알아보았습니다.
다음 포스팅에는 직접 스프링 코드로 구현해 보겠습니다. 간단하게 어떤 식으로 구성할 것인지 Flow에 대해서 설명드리고 직접 스프링을 이용하여 코드에 대한 설명을 이어나가겠습니다. 
 

다음 포스팅 (스프링 코드 구현) : 바로가기

 
읽어주셔서, 감사합니다.