개념 1. JWT 인증을 사용하는 이유, 리프레시 토큰이 Stateful해야 하는 이유
Intro.
JWT 인증 방식은 이제 많은 서비스가 메인으로 채택하는 대중적인 인증 방식 중 하나인데요. 사용자의 요청을 가볍고 빠르게 인가할 수 있는 장점이 있어, 분산 환경에서 가장 인기 있는 인증·인가 방식이죠.
하지만 JWT를 지금만큼 대중적으로 채택한 것이 긴 역사는 아닌 만큼, 개발자분들과 소통하거나, 여러 블로그를 탐색하다 보면 JWT에 대한 오개념이 곳곳에 산재해 있었습니다.
이렇게 많은 오해가 산재하는 환경이지만, 많은 사람들이 여러 블로그에서 그것에 대해 정확하게 다루고 있다고 기대하는 것 같았습니다. JWT가 대중적인 개념으로 보이는 만큼, 신뢰도 높은 정보로 정제되어 있다는 생각이 들었을 테니까요.
이 글에서는 JWT 액세스 토큰과 그것을 보조하는 리프레시 토큰에 대해 흔한 오해를 풀며, JWT 인증 방식에 대해 설명해 보겠습니다.
비교하기: 고전적인 세션 기반 사용자 인증
세션에 로그인 사용자 정보를 담으면 로그인이 완료되어 간단하고도 안전하게 구현할 수 있습니다.
세션과 쿠키는 전통적으로 웹 환경에서 사용되고 있는 대표적인 임시 저장 공간입니다.
세션: 서버마다 갖는 저장 공간 (Session - Server)
쿠키: 클라이언트가 갖는 저장 공간과 데이터 (Cookie - Client)
세션은 서버에 존재하기 때문에 외부에서 임의로 접근하기 어려워 보안이 좋습니다. 그래서 민감한 임시 데이터는 세션에 담는 관례 같은 것이 있었고, 로그인에도 다들 사용해 온 겁니다.
// 비밀번호 인증
if (!encoder.matches(rawPassword, encodedPassword)) {
throw ...
}
// 비밀번호 인증을 통과하면 세션에 사용자 정보를 담습니다.
session.setAttribute("SIGN_USER", signedUser);
분산 시 공유되지 않은 세션
과거에는 서버의 규모를 키우는 스케일업으로 버티는 곳도 많았지만, 현대에는 여러 가지 효율을 이유로 서버를 여러 개로 쪼개서 늘리거나 줄이는 ‘스케일아웃’을 선호하는 분위기입니다.
여기서 세션의 단점이 나타났는데, 세션은 서버에 속한 정보라서 여러 서버가 세션을 공유하지 않았기도 하고, 스케일 아웃의 장점을 살리려면 무리한 세션 공유도 줄여야 했으니.
당연히 로그인 정보도 서버 간에 공유되지 않아, 다음 그림으로 보면 사용자가 서버 A에 로그인을 했을 때, 서버 B는 로그인 사용자 정보를 갖지 않고, 결국 비회원으로 취급합니다.
우리말 번역
클라이언트: 안녕, 나는 1이라는 세션 아이디를 갖고 있어. 다들 나 알지?
서버 A: 안녕. 난 네가 나에게 담은 것들을 알지.
서버 B: 안녕! 나는 네가 서버 A에 뭘 담았는지 몰라.
이러한 문제로 개발자들은 분산 환경에서 유지할 수 있는 로그인 방식을 고민해야 했습니다.
분산 환경에서 시도할 수 있는 인증 방식들
스티키 세션 방식 Sticky Session
우선 사전 지식으로 사용자 요청의 전달 과정을 많이 축약하면 다음과 같습니다.
- 사용자 요청 → (인터넷) → 각종 네트워크를 거쳐서 → 서버에 도착
여기서 ‘각종 네트워크를 거치는’ 과정에서 로드밸런싱(부하 분산)이라는 것을 하는데, 이때 요청을 어느 서버로 보낼지도 정합니다. 스티키 세션 방식은 이 로드 밸런싱을 할 때, 클라이언트를 항상 같은 서버로 보내는 방식입니다.
어쨌든 동종서버들 내에서는 이 사용자를 담당하는 전용 서버가 할당된다는 개념이고, 다른 서버 그룹도 사용자를 담당하는 서버를 하나씩 할당할 테니, 담당 서버끼리만 세션을 공유해 두어도 되는 개념입니다.
✅ 분산 환경에서 사용자의 인증·인가를 할 수 있습니다.
✅ (공유 방식에 따라 다르지만) DB를 거치지 않기 때문에 빠릅니다.
🟥 부하 분산이 고루게 되는 것은 보장되지 않습니다.
🟥 각 서버가 저렴한 구성인 것이 스케일아웃의 장점인데, 메모리·디스크 사용량이 늘어날 수 있습니다.
세션을 공유하는 방식: 글로벌 세션
DB 서버에 세션을 공유합니다. 사용자 로그인 정보가 DB에 저장되기 때문에 로그인 여부를 공유할 수 있습니다.
✅ 분산 환경에서 사용자의 인증·인가를 할 수 있습니다.
✅ 부하 분산을 고루게 해도 됩니다.
✅ 각 서버의 메모리·디스크를 별로 소비하지 않습니다.
🟥 모든 인가에서 매번 DB를 거치기 때문에 느립니다. (치명적 단점)
세션을 공유하는 방식: 글로벌 - 로컬 동기화
DB 서버에 세션을 공유하고, 각 애플리케이션 서버가 이를 공유받아 자신의 세션에 동기화합니다.
✅ 분산 환경에서 사용자의 인증·인가를 할 수 있습니다.
✅ 부하 분산을 고루게 해도 됩니다.
✅ (동기화하고 나면) DB를 거치지 않기 때문에 빠릅니다.
🟥 실시간 동기화도 공짜가 아닙니다.
🟥 메모리·디스크 사용량이 늘어날 수 있습니다.
정리: 분산 환경의 인증 방식들
분산 환경에서 인증·인가 방식의 초점은 다음과 같습니다.
충분한 보안성을 보장해야 합니다.
(인증은 느려도 되지만) 인가는 거의 모든 요청에 포함되기 때문에 빨라야 합니다.
스케일아웃의 장점을 살리기 위해 과도한 자원에 의존하는 인가 방식을 삼갑니다.
사전 지식: HMAC 알고리즘 요약
전달 메시지 = 원문 메시지 + 해시 값
HMAC 알고리즘은, 송신자와 수신자가 서로 약속한 방식으로 메시지를 해싱하면서, 메시지가 변조되지 않았다는 것을 확인하는 방식입니다. 이를 메시지의 무결성을 보장한다고 표현합니다.
참고: HMAC의 대략적인 흐름
준비: 송신자와 수신자는 동일한 비밀 키를 미리 공유합니다.
발행: 송신자(생성, 전송) → 수신자
해시:
f(비밀 키, 원문 메시지) → 1차 해시 획득
해시:
f(비밀 키, 1차 해시) → 2차 해시 획득 (최종 HMAC 값)
두 번의 해싱은 키 은닉에 도움을 줍니다.
전달:
전달 메시지 = { 원문 메시지, 2차 해시 값 }
사용(무결성 확인): 송신자 → 수신자(무결성 확인)
수신자는 수신한 메시지 중 원문을 동일한 알고리즘으로 새로 해싱합니다. (동일한 비밀 키 사용)
수신자는 자신이 계산한 HMAC 값과 송신자가 보낸 HMAC 값이 일치하는지 확인합니다.
(일치하면) 위변조 된 메시지가 아니라고 확인하여, 발신자와 메시지를 신뢰합니다.
JWT 인증 방식에서 HMAC의 활용
HMAC 알고리즘으로 무결성을 확인하면 다음을 알 수 있다고 정리할 수 있습니다.
인증되지 않은 발신자에 의해 임의로 위조된 가짜 정보가 아님을 알 수 있습니다.
메시지가 전달 도중에 중간자에 의해 변조되지 않았음을 알 수 있습니다.
JWT 인증 방식을 HMAC의 전송·인가 구조로 보면 다음과 같습니다.
‘송신자 → (전송: 클라이언트가 전달) → 수신자’ 구조에서
송신자 = 인증서버
전송 구간 = (다른 네트워크 구간도 있지만) 클라이언트
수신자 = 다른 서버들(각종 API 서버 등)
이때 인증 서버는 JWT라는 토큰을 생성하고, 그것을 전달받아 확인하는 것이 다른 서버들입니다.
좀 더 JWT 인증스럽게 풀어서 보는 흐름은 다음 그림과 같습니다.
우리말 번역
인증 서버: “로그인되었습니다! 이 토큰을 받아서 당신의 요청에 첨부하세요.“
클라이언트: “그 토큰을 저장해 두고, 요청을 보낼 때 전달할게요.”
다른 서버들: “아, 저는 이 토큰을 만든 게 우리 쪽 서버들 중 하나라는 걸 알 수 있어요.“
JWT Access Token의 구조
JWT는 점(.)을 구분자로
HEADER.PAYLOAD.SIGNATURE
세 구간을 연결한 하나의 문자열입니다.
표준을 따르는 JWT 구조는 대략 다음과 같습니다.
원문 메시지
(전송을 위해 Base64로 인코딩한 비암호화 데이터. Base64는 암호화가 아니라 전송을 위한 인코딩.)
헤더(Header): 이 JWT에 대한 일종의 메타데이터입니다. (데이터에 대한 부가 정보)
페이로드(Payload): 탑재물을 말하는 페이로드는 우리가 옮기려는 데이터입니다. (혼모노)
HMAC 값
- 시그니처(Signature): 헤더와 페이로드를 해싱 한 HMAC 값입니다. (혼모노가 혼모노인지 확인)
JWT 인증 방식에서 인증을 할 때는 토큰 발행을, 인가를 할 때는 토큰 해석을 합니다.
각 API 서버들은 페이로드에 있는 데이터를 신뢰하고 사용할 수 있습니다.
페이로드에는 어느 사용자가 인가되어 있는지와 최소한의 사용자 정보를 담습니다.
- 절대 민감한 데이터를 담아선 안 됩니다. (페이로드는 비암호화 구간)
액세스 토큰의 수명 등을 페이로드에 담습니다.
‘권한(authority)’ 또는 ‘역할(role)’을 페이로드에 담습니다.
일반적인 구현에서는 하나의 역할이면 충분합니다.
여러 권한이나 역할을 담을 때는 인가에서 성능 지연이 생기지 않도록 합니다.
여러 권한이나 역할을 담을 때도 stateless한 방식을 유지하는 것이 좋습니다.
JWT 인증을 채택하는 이유
이러한 장점 때문에 JWT 인증 방식을 채택합니다.
분산 환경에서 인증·인가를 구현하기 위해서입니다.
DB를 거치지 않아 빠르기 때문입니다.
DB를 거치지 않기 위해 서버의 자원을 무리하게 낭비하지 않기 때문입니다. (stateless)
JWT 액세스 토큰의 문제점
통신 용량 증가는 적당한 비용입니다.
JWT 인증 방식을 사용하면 통신 용량이 살짝 증가하는, 무시해도 될 만큼 가벼운 이슈가 있지만 이것은 우리가 stateless 인증 방식의 장점을 위해 지불하는 비용이라 큰 문제가 되지 않습니다.
헤더와 Payload 오용을 조심하세요.
드물게 헤더와 페이로드가 암호화되어 있다고 오해하는 분들이 있습니다. 일부 사례는 여기에 비밀번호 해시 값을 담기도 하였다고 듣곤 합니다. 헤더와 페이로드에는 절대로 민감한 데이터를 담지 않고, 최소 정보만 담는 노력을 해야 합니다.
JWT 액세스 토큰은 ‘로그아웃한 척’을 할 수 있습니다.
JWT 액세스 토큰은 ‘즉시 만료’를 보장하는 기능이 없습니다. 반드시 수명이 다해야 만료됩니다.
‘프론트엔드’에서 토큰을 삭제할 수는 있지만 그것도 사용자가 그 프론트엔드 클라이언트만 사용할 때고, 토큰을 놓고 보면 굳이 그 프론트엔드를 사용하지 않아도 토큰을 담아 API 요청을 보낼 방법은 많습니다.
그래서 로그아웃도 ‘사용자에게 달린 기능’이 되기 때문에, 로그아웃을 시늉만 하고 실제로는 하지 않을 수 있는 거죠. 큰 문제는 다음으로 이어집니다.
JWT 액세스 토큰이 살아 있는 동안 이 사용자는 차단되지 않습니다.
이것은 오직 JWT 인증·인가에 모든 것을 맡길 때 이야기입니다.
JWT 액세스 토큰은 사용자가 삭제하지 않는 이상 메모장에만 옮겨 놔도 수명이 다할 때까지 사용할 수 있습니다. 이것이 stateless 인증·인가에 모든 것을 맡기는 사람들이 마주하는 문제죠.
심플하게도 이 문제는 JWT로 만든 모든 토큰의 수명을 짧게 관리하는 방식으로 일차적으로 해결하고, 이 토큰의 수명이 다하면 재발급을 받을 때 걸러내는 것을 기본 방식으로 합니다. 여기서 스포를 하자면, 이것이 리프레시 토큰을 JWT로 생성하지 않는 이유이기도 합니다.
추가: 블랙리스트 관리는 JWT의 장점을 떨어뜨립니다.
그 외에도 인가에서 블랙리스트를 조회하기도 하는데, 민감한 기능의 인가에 유용한 방식입니다. 하지만 모든 JWT 인가에 블랙리스트를 사용하면, DB를 거치지 않아서 빠르다는 JWT의 장점을 거의 버리는 방식이 됩니다. 이러면 위에 잠깐 언급한 글로벌 캐시를 통한 세션 공유 방식보다 특별히 메리트가 없고, JWT 때문에 통신 용량까지 증가시켜 단점을 증폭시키는 방식이 될 겁니다. 대신 로그인된 사용자 목록 조회보다 크기가 작은 블랙리스트에서 조회가 더 빠르겠지만, ‘불행 중 다행식’ 장점을 찾을 게 아니라, 이 방식이 JWT의 장점을 거의 완전히 버리는 방식이라는 것이 중요합니다.
조금 더 보완하는 방식은 JWT 인가 개념 포스팅에서 블랙리스트 관리를 가볍게 다루겠습니다.
Stateful 리프레시 토큰
리프레시 토큰은 액세스 토큰의 ‘수명이 짧아서’ 사용하는 게 아닙니다.
액세스 토큰의 ‘수명을 짧게 하기 위해서’ 사용하는 게 진짜 목적입니다.
JWT 인증·인가 방식은 stateless의 장점을 가져다 줌과 동시에, 만료시킬 수 없다는 치명적인 단점을 가져다 주었습니다. 그렇다면, ‘중간 중간 stateful하게 인가를 해서 확인하는’ 방식이 가능하다면, 그런 방식을 사용해서 보완할 수 있을 겁니다.
그것이 바로 ‘리프레시 토큰’입니다.
조금 더 구체적으로는 다음 두 가지를 충족하면 됩니다.
액세스 토큰의 수명은 짧게 둡니다.
Stateful한 리프레시 토큰을 사용합니다.
이로써 사용자는 “중간중간 refresh token을 사용해서 인가하기”를 하게 됩니다.
수명이 짧은 액세스 토큰이 자주 만료됩니다.
사용자는 리프레시 토큰을 사용해 서버에 리프레시를 요청합니다.
이때 서버는 사용자가 아직 자격을 갖고 있는지 확인합니다.
사용자가 리프레시를 받아도 된다면 새 액세스 토큰을 발급해 줍니다.
일반적으로 이때 리프레시 토큰도 갱신합니다.
새 리프레시 토큰 발급으로 기존 리프레시 토큰이 만료됩니다.
새 리프레시 토큰 발급으로 로그인 유지 수명이 연장됩니다.
사용자는 액세스 토큰이 빈번하게 만료되어 중간중간 리프레시 토큰을 사용해야 하기 때문에 stateful 인가 방식이 주기적으로 간섭할 수 있는 구조가 완성됩니다.
그래서 리프레시 토큰은 JWT로 생성하지 않습니다.
리프레시 토큰은 stateful한 관리가 중요하기 때문에 JWT로 생성하지 않고 암호학적으로 안전한 랜덤 함수를 사용해 생성하는 것을 권합니다. 자세한 것은 다음 글에서 다룹니다.
리프레시 토큰은 탈취되면 기본적으로 답이 없습니다.
리프레시 토큰은 한번 발급되고 나면, 서버에서 삭제하기 전까지 사용자가 액세스 토큰 발급에 사용할 수 있습니다. 마치 로그인을 대신하는 비밀번호 대체 값과 같은 역할입니다.
로그인 유지에 사용하는 만큼, 한번 탈취된 토큰은 (별도 탐지와 조치를 하지 않으면) 새 토큰의 발급에 반복적으로 사용되면서 지속적으로 인가를 유지할 수 있습니다. 만료되기 전에 재발급을 받는다면요.
적어도 서버에서 유출되는 일은 없어야 합니다.
비슷한 사례로 비밀번호 해시 값 유출은 생각보다 빈번한 일입니다.
비밀번호 해시 값이 수십만에서 수천만 개까지 유출이 되어도 비교적 안전했던 이유는 단방향 암호화를 한 상태로 저장하고, 경우의 수가 많은 만큼, 유추하기 어려운 비밀번호는 초당 수억 개씩 찍어 보더라도 인간의 수명보다 더 긴 시간이 필요하며, 현대적인 주요 단방향 암호화 함수들이 하드웨어 저항성을 갖고 있어서 초당 수억 개씩 찍어볼 수도 없기 때문입니다. 물론 유추하기 쉬운 비밀번호는 금방 시도되기 때문에 해시 유출이 되지 않도록 방지하는 게 우선이지만요.
리프레시 토큰도 비밀번호를 대신할 수 있다면 유출에 대비해야 합니다.
그래서 리프레시 토큰도 서버에 저장할 때 해싱이라도 하는 게 좋습니다. 물론 비밀번호에 비해 랜덤하게 생성하여 쉽게 유추할 수 없는 리프레시 토큰은, 비밀번호만큼 보안을 위해 사용자 경험을 많이 양보하지 않아도 적절한 균형일 수 있습니다. 예를 들어 비밀번호 암호화에 1초를 사용한다고 해도, 리프레시 토큰 암호화는 그만큼 긴 시간을 사용하지 않아도 되죠.
초점은, 리프레시 토큰이 사용자의 요청 도중 ‘인가’ 과정에 가끔 간섭하기 위해 사용되고, 이때 사용자는 로그인을 유지하는 작업(리프레시)이 수행되고 있다는 인지 없이 API 응답이 잠시 느려지는 듯한 경험을 한다는 것입니다. 리프레시가 액세스 토큰의 수명이 만료될 때만 수행되는 만큼, 사용자 경험을 과도하게 해치지는 않지만, 우리가 로그인에 30초를 사용하지 않는 것처럼, 리프레시에도 1~2초를 사용할 필요가 없다면 조율이 가능하다는 것입니다. 이것은 팀과 회사의 결정에 따를 수 있습니다.
양방향 암호화를 할 필요가 없기 때문에, 보안성이 더 강한 단방향 암호화를 합니다.
리프레시 토큰은 ‘맞는지 확인'만 하는 용도입니다. 우리는 이런 것에 대해 원문으로 복호화를 하지 않고, 해시 값 상태로 비교하면 된다는 것을 비밀번호 비교에서 자주 관찰할 수 있습니다.
리프레시 토큰도 값이 일치하는지 비교하는 것이 목적이고, 원문의 내용을 복원해서 별도 작업을 할 일이 없는 데이터입니다. 그래서 보안성이 강한 단방향 암호화를 택하는 것이 좋습니다. 성능은 원하는 범위로 조절하면 되지만, 단방향 암호화는 복호화가 없기 때문에 보안 및 성능 요구사항을 도중에 바꾸려면 기존 암호화 방식과 병행하는 구조를 설계해 운영해야 합니다.
전송 구간에서 탈취되지 않아야 합니다.
그래서 리프레시 토큰은 Secure 옵션이 있는 쿠키에 담아서 HTTPS 환경에서만 함께 전송되도록 해야 합니다. 비밀번호를 다루는 모든 요청이 HTTPS를 사용하는 것처럼 리프레시 토큰도 HTTPS 환경에서 전달되어야 합니다.
사용자 측 공격에 기본적인 면역이 있어야 합니다.
클라이언트 측으로 다양한 공격 시도가 있을 수 있습니다. 흔하게 언급되는 XSSCross Site Scripting 공격, CSRFCross Site Request Forgery 공격에 면역을 갖게 설계해야 합니다.
XSS(Cross Site Scripting)
XSS 공격은 유형이 나뉘지만, 모두 악의적인 소스 코드가 사용자의 환경에 전달되어 실행되게 합니다. 현대적인 프론트엔드 기술은 이 공격에 많은 면역을 갖는 환경을 제공하지만, 일부 동적인 기능은 여전히 관련된 위협을 내포할 수 있습니다. 따라서 XSS 발생 루트를 프론트엔드와 백엔드 모두에서 차단합니다.
전체 소스코드에 기본적인 XSS 방어 조치가 완료되어 있는지를 떠나서, 리프레시 토큰의 탈취 가능성을 차단해야 합니다. 어느 작업자의 실수로 XSS 공격을 받을 수 있는 취약점이 발생해도 리프레시 토큰에 접근할 수 없도록 HttpOnly
옵션을 사용할 수 있습니다.
HttpOnly
옵션은, 이 쿠키를 브라우저만 받고, 프론트엔드 소스 코드가 동작하는 환경에는 제공해 주지 않겠다는 약속입니다. 정상적인 브라우저는 프론트엔드 스크립트에서는 HttpOnly
옵션이 있는 쿠키에 접근할 수 없게 하고, 서버에 요청을 보낼 때 자동으로 이 쿠키를 붙여서 보냅니다.
사용자가 정상적인 브라우저, 정상적인 네트워크를 이용해야 하며, 사용자가 이상한 브라우저나 취약한 네트워크를 이용하는 것은 여전히 사용자에 의해 발생할 수 있는 취약점입니다.
CSRF(Cross Site Request Forgery)
사용자의 자격이 탈취되는 일을 방지해야 합니다.
CSRF 공격은 주로 쿠키에 대해 시도될 수 있는데요. 브라우저가 요청에 쿠키를 자동으로 담아서 보내기 때문에, 공격자가 이를 자신에게 보내게 하여 탈취하려는 시도가 있을 수 있습니다. 이 또한 웹 표준이 제공하는 쿠키 옵션으로 해결되며, SameSite=Lax
또는 SameSite=Strict
옵션을 추가하면 브라우저는 외부 도메인에 보내는 요청에는 이 쿠키를 담지 않을 것을 약속합니다.
사용자가 정상적인 브라우저, 정상적인 네트워크를 이용해야 하며, 사용자가 이상한 브라우저나 취약한 네트워크를 이용하는 것은 여전히 사용자에 의해 발생할 수 있는 취약점입니다.
리프레시 토큰을 담은 쿠키에 사용하는 옵션 정리
쿠키에 필요한 옵션을 정리하면 다음과 같습니다. 각각 XSS 방어, 전송구간 암호화 요구, CSRF 방어를 위한 옵션이며, 이것이 누락된 것만으로 곧바로 유출되는 것은 아니지만, 모든 보안이 완벽하지 않을 때 이 옵션으로 서버와 정상적인 브라우저가 협업하여 리프레시 토큰 쿠키를 보호해 줄 것입니다.
HttpOnly
Secure
SameSite=Lax 또는 SameSite=Strict
CORS(Cross Origin Resource Sharing)는 보조적입니다.
간혹 CORS 설정을 통해 방어할 수 있다고 오해하는 분들도 있지만, 우회 수단을 통한 공격에는 면역이 전혀 없기 때문에 직접적인 방어 수단이 아닙니다. 극단적으로 말하면, 이 보호에 관련해서는 CORS가 거의 도움이 되지 않을 수도 있습니다. 이미 탈취한 토큰이 있다면 자신을 위장하여 공격하는 것이 매우 쉽기 때문입니다. (CORS에 대한 자세한 설명은 본문과 무관하여 생략합니다.)
사용자에 의한 유출은 직접 막을 수 없습니다.
인증과 인가는 사용자와 서버 간 신뢰성에 대한 협약입니다. 그 기밀성을 사용자 쪽에서 깬다면, 서버는 그것을 직접 방지할 수단이 없습니다. 그래서 마치 비밀번호처럼, 사용자의 부주의로 인한 유출은 서버가 직접 막지는 못합니다. 일부 공격 기법에 면역을 갖는 구조로 운영할 수는 있습니다만, 기본적인 조치일 뿐, 사용자 측의 모든 유출 수단을 없앨 수 없습니다.
하지만 이상한 활동을 탐지할 수는 있습니다. 우리는 사용자의 활동 이력을 수집해 사이트의 보안 강화에 사용할 수 있습니다. 예를 들어 UA(User Agent)를 수집해서 사용자가 접속한 환경을 확인할 수 있고, 우회하여 은닉된 IP가 아니라면 IP 주소로 사용자의 대략적인 마지막 접속 위치를 알 수 있습니다. 이때 일부 활동의 수집은 사용자의 동의가 필요할 수 있습니다.
리프레시 토큰은 접속 환경당 하나씩이기 때문에, 여러 접속 환경을 사용하는 사용자는 리프레시 토큰을 복수로 사용할 수 있습니다. 하지만 적어도 각 토큰의 접속 환경은 불변이어야겠죠. 만약 다른 환경에서 토큰을 사용해 리프레시 요청을 한다면, DB에 잘 저장된 토큰이더라도 환경 불일치를 탐지합니다.
일부 서비스는 이 토큰이 각각 어느 환경에서 사용되고 있는지 사용자에게 공개하는 방식으로(로그인 된 기기 목록 등) 사용자 스스로 보안 모니터링에 동참하도록 유도합니다. 그리고 사용자 IP로 접속 국가를 알 수 있기 때문에, 사용자에게 기기별 마지막 접속 국가를 안내하거나, 평소 사용하지 않던 국가에서의 요청 발생 시 등록된 연락수단으로 알림을 보내거나, 사용자가 허용하지 않는 국가 IP로 리프레시 요청을 하면 무시하도록 사용자가 설정하는 등의 기능을 제공할 수 있습니다.
사용자 과실로 인한 유출은 시나리오가 많은 만큼 모두 막을 수 없고, 정상적인 패턴과 대부분 일치하는 탈취 패턴은 탐지부터 까다롭습니다. 단지 우리는 보안을 강화하기 위해 다양한 시나리오를 탐색하면서 조치를 추가하는 거죠.
이전 리프레시 토큰 이력을 보존하면 좋습니다.
사용되어 만료된 리프레시 토큰을 적어도 기존 수명까지는 보존할 수도 있습니다. 이렇게 하면 리프레시 토큰의 재사용 시도를 탐지할 수 있습니다. 액세스 토큰을 리프레시 할 때, 보통은 리프레시 토큰도 함께 재발급을 하기 때문에 리프레시 토큰을 안전한 일회성으로 사용합니다.
사용자 과실에 의한 유출의 탐지나 방지는 선택적일 수 있습니다.
좋은 이야기만 하고 싶지만, 수많은 시나리오를 커버할 만큼 활동을 탐지하며 보안을 강화하는 것이 일반 사용자 계정에 필요한지는 많은 기업들에게 고민일 수 있습니다. 관련된 보안 조치는 대부분 선택적이며, 사용자 과실로 인한 유출 탐지 및 방지에 리소스 과투자를 막는 것을 합리적으로 생각하는 곳이 많습니다.
각 구간의 내용을 최대한 축약했습니다만, 항목이 많아서 전체 분량이 늘어났네요.
이 글에서 다룬 내용을 정리하자면 다음 내용들이 주류였던 것 같습니다.
JWT 인증 방식의 장점은 분산 환경에서도 지연이 거의 없이 빠르게 인가할 수 있다는 것
stateless한 특징이 장점이지만 수명 내에는 만료가 없는 무적이기 때문에 수명을 짧게 둔다는 것
리프레시 토큰으로 stateful한 인가 과정을 중간중간 넣을 수 있어서 안전하다는 것
블랙리스트 관리는 인가를 지연시키지 않는 방식을 고민해야 한다는 것
다음 글은 JWT 액세스 토큰의 생성과 전달, 리프레시 토큰의 생성, 전달, 보존 등을 다룹니다.
Subscribe to my newsletter
Read articles from Merge Simpson directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Merge Simpson
Merge Simpson
Hello, I am Korean. Welcome, visitor. You are very cool. 안녕하세요, 저는 한국어입니다. 방문자여 환영한다. 당신은 매우 시원해.