Access Token & Refresh Token

worldclasscitizen·2026년 1월 10일

SSAFY

목록 보기
4/9

여는 말

사용자 인증 기능에는 Token이라는 개념이 존재합니다.

Access Token(이하 AT) 은 실질적으로 사용자가 API를 통해서 요청을 보낼 때마다 접근 권한을 판단하는 토큰이고,
Refresh Token(이하 RT)은 AT가 만료되었을 때 재발급을 위해 사용하는 토큰입니다.

AT가 만료되면 RT가 유효한지 확인한 뒤 AT를 재발급합니다.
일반적으로 AT TTL이 더 짧게 설정되기 때문에 RT가 먼저 만료되는 경우는 드뭅니다.

두 토큰은 용도가 다르기 때문에, 관리하는 방식도 달라집니다.


어떤 정보를 가지고 있나요?

AT는 인증을 위한 토큰이므로, 사용자 식별 및 권한 판단에 필요한 최소한의 정보를 가지고 있습니다.
상황에 따라서 자주 변경되지 않는 비민감 정보까지도 담을 수 있습니다.

RT는 재발급을 위한 토큰이고, 서버 저장소에서 사용자/만료 여부/기기/재사용 여부 등의 상태를 관리하는 방식이 흔합니다.


어디에 저장되나요?

일단 RT는 보통 HttpOnly 쿠키로 전달하고, 서버 측(DB/Redis)에 해시/상태로 저장합니다.

AT/RT를 다루는 방식은 현업에서 크게 아래 3가지 패턴으로 정리할 수 있습니다.

1. SPA 패턴
2. BFF/세션 패턴
3. 쿠키에 토큰 자체를 담는 패턴


  1. SPA 패턴

브라우저가 AT를 들고 API를 직접 호출하는 방식입니다.

일반적으로 AT는 프론트엔드 인메모리 변수에 두고, RT는 HttpOnly 쿠키로 관리합니다.

흐름은 다음과 같습니다.

  1. 사용자가 로그인을 하면 서버가 AT를 발급하고, RT는 HttpOnly 쿠키로 내려줍니다.
  2. 이후 브라우저는 API 요청마다 AT를 Authorization 헤더에 담아 전송합니다.
  3. AT가 만료되면 브라우저는 /refresh 같은 재발급 API를 호출하며, RT는 HttpOnly 쿠키로 자동 전송됩니다.
  4. 서버는 RT를 검증한 뒤, 새 AT를 발급합니다.
  5. 로그아웃 시에는 보통 RT를 더 이상 사용하지 못하도록 처리합니다.
    서버 저장소에 저장된 RT 상태를 폐기(revoke)하거나, Refresh Token Rotation(RTR) 전략을 적용해서 재사용을 차단합니다.

RTR은 AT 재발급 시 RT도 함께 교체하는 방식입니다.
RT를 최대한 짧게 유지하고, 재사용을 불가능하게 만들어서 보안을 강화합니다.


  1. BFF(Backend For Frontend)/세션 패턴

브라우저에는 토큰을 노출하지 않고, 서버가 토큰을 대신 보관/사용하는 방식입니다.

보안성과 통제력이 뛰어납니다.

흐름은 다음과 같습니다.

  1. 사용자가 로그인을 하면 BFF 서버가 AT, RT를 발급하거나, 인증 서버로부터 받아옵니다.
  2. BFF 서버는 DB/Redis에 sid -> AT, RT, 만료 정보, 상태 형태로 저장합니다.
  3. 이후 브라우저에는 sid만 담긴 세션 쿠키(HttpOnly, Secure, SameSite)를 내려줍니다.
  4. 브라우저는 인증이 필요한 요청마다 sid 쿠키를 자동으로 전송합니다.
  5. BFF 서버는 sid로 저장소를 조회해 토큰을 꺼낸 뒤, 그 토큰으로 백엔드 API를 호출합니다.
  6. 로그아웃/강제 만료는 sid에 해당하는 레코드를 폐기하면 즉시 반영됩니다.

  1. 쿠키에 토큰 자체를 담는 패턴

AT, RT 자체를 암호화해서 쿠키에 담는 방식입니다.

Redis가 필수는 아니라는 장점이 있지만,
토큰이 너무 길어지면 4KB의 쿠키 용량 제한에 걸릴 수 있다는 단점이 있습니다.
또, 쿠키가 탈취되면 암호화 여부와는 별개로 그대로 재사용될 수 있기 때문에 추가적인 방어 로직이 필요합니다.


번외 1-1) JWT

잠깐 JWT에 관해서 알아보고 갑시다.

[ 1 ] Opaque Token
우리가 토큰을 만들 때 그냥 랜덤 문자열로 만들면, 그건 Opaque Token 또는 불투명 토큰이라고 부릅니다.
그냥 복잡한 랜덤 값이기 때문에, 서버가 클라이언트로부터 Opaque Token을 받으면 서버 저장소에 접근하기 위한 Key로 사용합니다.

[ 2 ] JWT
토큰을 JWT 포맷으로 구현하면, Base64Url로 인코딩된 JSON의 형식을 가집니다.
이렇게 만들어진 토큰은 header.payload.signature처럼 점으로 구분된 세 파트로 작성되어 있습니다.

  • Header : 토큰의 타입(JWT), 사용된 해시 알고리즘(HS256 등) 정보를 담고 있습니다.
  • Payload : 실제 데이터(user_id, email, role, exp 등)가 JSON 형식으로 담겨 있습니다.
  • Signature : Header, Payload를 비밀키로 해싱한 값입니다. 위변조를 방지하기 위함입니다.

서버는 클라이언트로부터 JWT 토큰을 받으면, 서버 저장소를 살펴볼 필요가 없습니다. 저장이 안 되어 있기 때문이죠. (Stateless)
단지, 토큰의 Signature가 유효한지만 검사하면 됩니다.


참고로 우리가 흔히 쓰는 JWT는 암호화되어 있는 게 아니라, 인코딩되어 있는 JWS(JSON Web Signature) 구조입니다.
JWE(JSON Web Encryption)라는 규격으로 암호화할 수도 있긴 한데, 일반적으로 자주 사용되지는 않습니다.

JWS가 데이터 위변조를 방지하기 위함이라면 JWE는 내용을 아예 읽을 수 없게 만드는 것이 목적입니다.

  • 민감한 정보를 토큰 내에 어쩔 수 없이 담아야 한다거나,
  • MSA 환경에서 서비스 A가 서비스 B로 비밀키나 다른 시스템의 접근 토큰 등을 전달해야 한다거나,
  • 엄격한 보안 규정을 적용해야 하는 금융/의료 등의 특수한 도메인일 때

JWE를 사용합니다.


아무튼 저희는 보통 JWS를 사용하기 때문에, 누구나 Base64로 디코딩하면 Payload 내부를 들여다 볼 수 있어서 민감한 정보는 절대 JWT에 넣어서는 안 됩니다.


이쯤에서 또 아셔야 할 게,
JWT의 가장 큰 장점은 서버가 상태를 저장하지 않는 Stateless라는 점입니다.
하지만 이러한 장점 때문에 이미 발급된 토큰을 서버가 제어할 수 없다는 단점이 생깁니다.

사용자가 로그아웃을 하거나, 관리자가 특정 사용자를 차단하는 상황을 가정해 봅시다.
Opaque Token 방식이었다면, 서버 저장소에서 해당 토큰 레코드를 삭제하면 됩니다.
하지만 JWT는 서버가 토큰을 관리하지 않기 때문에, 해커가 토큰을 탈취했다면 그 토큰이 만료되기 전까지 악용될 수 있습니다.

그래서 Redis를 활용한 Blacklist 방식을 사용하게 되는데요.
Redis를 사용한다는 것 자체부터가 Stateful입니다.


그래서 결론이 뭐냐구요?
Stateless + Stateful을 둘 다 채택하는 하이브리드 방식이 통용됩니다.


번외 1-2) Redis에 Blacklist를 저장할 거면 처음부터 토큰을 Redis에 저장하면 되지 않나요?

네. 안 됩니다.

1. 데이터의 양 자체가 다릅니다.
Redis 세션 방식(= Opaque Token)을 사용하면, 동시 접속자가 100만 명일 때 100만 개의 토큰이 Redis에 존재합니다.
JWT Blacklist 방식을 사용하면, 로그아웃을 한 사용자의 토큰만 저장합니다.

2. MSA 환경에서 비효율적입니다.
서비스가 수십 개로 쪼개져 있는 MSA 환경이라고 가정해 봅시다.
Redis 세션 방식은 모든 서비스에서 유저 정보를 얻기 위해 중앙에 있는 Redis에 접근해야 합니다.
JWT Blacklist 방식은 토큰 자체에 정보가 담겨 있기 때문에 Redis로 접근할 필요가 없습니다. Blacklist 여부 확인이 필요한 작업에서만 요청하면 됩니다.

이처럼 시간과 메모리 면에서 다릅니다.


번외 2) 데이터 암호화

여기서는 암호화를 할 때 대칭키 방식을 사용합니다.
암호화를 하는 주체와 복호화를 하는 주체가 동일하기 때문이죠.
서버만 알고 있는 비밀키 하나로 빠르게 처리할 수 있습니다.

업계 표준으로 사용되는 알고리즘은 AES-256-GCM입니다.

0개의 댓글