로그인 시스템 설계: 세션·토큰 회전·기기 관리·로그아웃 전파
사용자가 의심스러운 기기를 로그아웃했는데 그 기기에서 몇 분 더 API(Application Programming Interface, 앱과 서버가 정해진 요청과 응답으로 통신하는 접점)가 열린다면, 화면의 성공 메시지는 거짓에 가깝다. 로그인 시스템 설계는 비밀번호를 맞히는 입구만 만드는 일이 아니다. 사용자가 로그인 상태를 유지하고, 현재 연결된 기기를 확인하고, 위험할 때 그 권한을 실제로 끊을 때까지 하나의 약속을 지키는 일이다.
이 글은 웹과 모바일을 함께 쓰는 사용자의 가입·로그인부터 세션 유지, 기기별 로그아웃과 전체 로그아웃까지 따라간다. 2026년 8월 16일 기준 미국 국립표준기술연구소, 인터넷 표준 문서, OpenID Foundation과 Open Worldwide Application Security Project(OWASP) 보안 지침을 대조한 제품 중립 설계이며 특정 서비스의 내부 구조나 운영 성능을 설명하지 않는다. 처리량, 지연 목표와 보존 기간은 선택을 비교하기 위한 설계 가정이다.
20초 핵심 요약
- 무엇: 짧게 사는 접근 권한과 서버가 추적하는 기기별 세션을 결합한다.
- 왜: 토큰만 오래 믿으면 탈취와 로그아웃 전파 지연이 커지고, 매 요청마다 중앙 세션을 읽으면 지연과 장애 의존성이 커진다.
- 어떻게: 로그인, 토큰 갱신, 기기 로그아웃의 API와 상태 변화를 따라가며 용량·장애·대안을 비교한다.
사용자가 원하는 것은 빠른 입장과 확실한 퇴장이다
이 설계의 사용자는 웹과 모바일에서 같은 계정을 쓰는 사람이다. 대표 행동은 세 가지다.
- 가입하거나 로그인한 뒤 빠르게 원래 하던 작업으로 돌아간다. 잘못된 비밀번호 안내가 다른 사람에게 계정 존재 여부를 과도하게 알려서는 안 된다.
- 자주 쓰는 기기에서는 로그인 상태를 유지한다. 새 기기나 비밀번호 변경처럼 위험이 큰 행동에서는 추가 확인을 받아들일 수 있다.
로그인된 기기화면에서 의심스러운 연결 하나를 끊거나 모든 기기에서 로그아웃한다. 성공 표시 뒤에는 실제 권한도 정해진 시간 안에 사라져야 한다.
MFA(Multi-Factor Authentication, 다중 인증)는 비밀번호 외에 다른 종류의 증거를 하나 더 확인하는 절차다. 이 글에서는 새 기기나 민감 행동의 위험을 낮추기 위해 필요하다. 복구 절차가 MFA보다 약하면 공격자가 그 우회로를 택할 수 있으므로 복구도 같은 위험 수준으로 다뤄야 한다.
포함 범위는 이메일이나 사용자명 기반 가입, 비밀번호 검증, MFA 등록과 도전, 웹·모바일 세션, 접근·갱신 토큰, 기기 목록, 기기별·전체 철회, 위험 판단과 감사 기록이다. 본인확인, 역할별 권한, 소셜 로그인 사업자의 비공개 구조와 비밀번호 없는 가입 절차 전체는 제외한다.
품질 목표도 사용자의 언어로 정한다. 정상 로그인은 p95, 즉 요청 100개 중 느린 쪽 다섯 번째가 500ms 이내여야 한다. 외부 MFA 전송 시간은 제외한다. 기존 세션으로 API를 부를 때 인증이 더하는 시간은 p95 10ms 이내, 기기 로그아웃은 1초 안에 성공을 표시하고 민감 API는 철회 후 5초 안에 거부하는 것이 이 글의 설계 목표다. 이는 실측값이나 공식 요구가 아니다.
정확성에는 타협하지 않는다. 다른 사용자의 세션을 보여주거나 끊어서는 안 된다. IP(Internet Protocol) 주소, 위치와 기기 정보는 위험을 추정하는 단서일 뿐 사람이나 물리 기기를 확정하는 증거가 아니다. 최소한만 수집하고, 짧게 보존하며, 사용자가 이해할 수 있는 위험 사유를 남긴다.
규모 가정은 로그인보다 반복 인증의 부담을 보여준다
다음 값은 실제 서비스 통계가 아니라 용량 계산용 입력이다.
| 항목 | 설계 입력 | 계산 결과 | 선택에 미치는 영향 |
|---|---|---|---|
| 월간 활성 사용자 | 1,000만 명 | 가정 | 사용자와 자격 증명 저장소의 분할 기준이 된다 |
| 일간 활성 사용자 | 100만 명 | 매일 20% 로그인 시 20만 회/일 | 평균의 10배 피크는 약 24 QPS(Queries Per Second, 초당 요청 수)지만 공격과 이벤트를 고려해 200 QPS 여유를 둔다 |
| 활성 기기 | 사용자당 평균 2.5개 | 2,500만 세션 | 세션당 인덱스 포함 1KB 가정 시 복제 전 약 25GB다 |
| 인증된 API 피크 | 초당 20,000건 | 가정 | 모든 요청이 중앙 DB를 읽으면 병목이 된다 |
| 인증 사건 | 하루 500만 건, 건당 500B | 하루 2.5GB, 90일 약 225GB | 압축·복제·인덱스 전에도 감사 저장소를 분리할 이유가 생긴다 |
활성 기기가 평균 10대로 늘면 세션 원본은 약 100GB가 된다. API 피크가 10배가 되면 지역별 철회 캐시, 즉 자주 확인하는 철회 상태를 가까운 저장 공간에 복사해 둔 값과 서명 키 배포가 먼저 부담을 받는다. 반대로 사용자 수가 수십만이고 단일 지역에서 운영한다면 관계형 데이터베이스 하나와 캐시로 시작하는 편이 비용과 운영 복잡도가 낮다.
로그인은 비밀번호 확인 뒤에도 두 번 더 판단한다
첫 행동은 가입과 로그인이다. 요청이 들어온 뒤 사용자가 서비스로 돌아가기까지의 흐름은 다음과 같다.
- 사용자가 이메일과 비밀번호, 약관 버전을
POST /v1/signup으로 보낸다. 가입 처리는 중복을 확인하고 비밀번호를 Argon2id로 해시해 저장한다. 해시는 원문을 되돌리는 암호화가 아니라 입력을 한 방향으로 바꾸는 계산이며, Argon2id는 계산 시간과 메모리를 쓰게 해 유출된 값의 대량 추측을 어렵게 한다. 비밀번호마다 고유한 salt, 즉 같은 비밀번호도 다른 결과가 되게 섞는 값을 둔다. - 이메일 확인 뒤 사용자가
POST /v1/login을 보낸다. 속도 제한기는 계정·IP·네트워크별 실패를 살펴 추측 공격을 늦춘다. 한 계정만 잠그면 공격자가 그 사용자를 서비스에서 쫓아낼 수 있으므로 점점 길어지는 대기와 추가 확인을 함께 쓴다. - 위험 판단기는 새 기기, 비정상적인 이동, 알려진 악성 IP와 실패 패턴을 평가한다. 위험이 낮으면 계속하고, 중간이면 MFA를 요구하며, 높으면 거부하고 복구 경로를 안내한다. 이 구성 요소는 비밀번호 성공을 최종 신뢰로 곧바로 바꾸지 않는 역할만 맡는다.
- 인증 서비스는 기기 세션을 만들고 접근 토큰과 갱신 토큰을 발급한다. 접근 토큰은 API가 현재 요청의 권한을 빠르게 확인하는 짧은 수명의 값이다. 갱신 토큰(refresh token)은 비밀번호를 다시 입력하지 않고 새 접근 토큰을 받는 장기 비밀이며, 탈취 피해를 제한하려면 서버가 사용 이력을 추적해야 한다.
- 웹에서는 JavaScript가 읽지 못하게 하는
HttpOnly, HTTPS(Hypertext Transfer Protocol Secure, 통신 내용을 암호화하는 웹 전송 방식)에서만 보내는Secure, 다른 사이트에서의 전송을 제한하는SameSite속성을 둔 쿠키를 우선 검토한다. 모바일은 운영체제의 보호 저장소를 사용한다는 경계까지만 정한다.
대표 요청에서는 다음처럼 비밀값을 로그에서 가린다.
POST /v1/login
Content-Type: application/json
{"login":"[email protected]","password":"<redacted>","device_install_id":"dvc_public_id"}
추가 확인이 필요하면 서버가 허용된 방식과 한 번만 쓸 도전 식별자를 돌려준다.
{
"status": "mfa_required",
"challenge_id": "ch_opaque",
"allowed_methods": ["webauthn", "totp"]
}
TOTP(Time-based One-Time Password)는 시간에 따라 바뀌는 일회용 번호다. 배포와 복구가 비교적 쉽지만 사용자가 번호를 직접 입력하므로 피싱에 강한 수단은 아니다. WebAuthn은 사용자의 기기와 서비스 주소를 암호학적으로 확인하는 웹 인증 규격이며, 관리자나 고가치 행동처럼 피싱 저항성이 필요한 곳에서 우선할 수 있다.
세션 유지는 짧은 접근과 추적 가능한 갱신으로 나눈다
두 번째 행동은 로그인 상태를 유지하는 것이다.
- 사용자가 API를 호출하면 서버는 접근 토큰의 서명, 발급자, 대상 API와 만료 시각을 확인한다.
aud(audience)는 토큰을 받아도 되는 API를 뜻한다. 이 범위를 좁히면 한 API에서 유출된 토큰이 다른 API까지 여는 피해를 줄일 수 있다. - 접근 토큰이 만료되면 클라이언트는 현재 갱신 토큰을
POST /v1/token/refresh에 한 번 보낸다. - 인증 서비스는 받은 원문을 그대로 저장하지 않고 해시해 찾는다. 토큰 상태, 연결된 기기 세션과 절대 만료를 확인한 뒤 새 접근 토큰과 새 갱신 토큰을 하나의 데이터베이스 처리로 만든다. 이전 토큰은
used상태가 된다. - 회전(rotation)은 장기 비밀을 사용할 때마다 새 값으로 교체하는 방식이다. 이미
used인 값이 다시 오면 복제된 비밀의 재사용일 수 있으므로 같은family_id, 즉 한 번의 로그인에서 이어진 토큰 계보와 기기 세션을 끊고 재로그인을 요구한다.
이 과정에는 정상 사용자를 공격자로 오인할 수 있는 경계가 있다. 브라우저의 두 탭이 같은 갱신 토큰을 거의 동시에 보내면 첫 요청은 성공하고 둘째 요청은 이미 사용된 값을 본다. 서버는 토큰 행을 잠가 한 번만 소비하게 하고, 클라이언트는 같은 기기 설치 단위에서 갱신을 직렬화한다. 직렬화는 여러 갱신을 동시에 보내지 않고 앞 요청이 끝난 뒤 다음 요청을 보내는 방식이다.
네트워크 재시도에는 별도 요청 식별자와 매우 짧은 결과 캐시를 둘 수 있다. 같은 설치에서 곧바로 반복된 요청에는 앞선 결과를 제한적으로 돌려주되, 다른 기기나 위험한 네트워크 신호가 붙은 재사용은 계보를 즉시 철회한다. 유예 시간을 늘리면 재로그인은 줄지만 공격자가 옛 토큰을 다시 쓸 창도 길어진다. 정확한 유예는 공식 규격이 아니라 서비스가 위험과 사용성을 측정해 정할 값이다.
로그아웃 성공은 서버 원본에서 시작해 연결 서비스까지 퍼진다
세 번째 행동은 현재 기기를 확인하고 권한을 끊는 것이다.
GET /v1/me/sessions는 요청한 사용자 소유의 세션만 최근 사용 순으로 반환한다. 현재 기기 여부, 추정 플랫폼, 첫 로그인, 최근 사용, 대략적 위치와 위험 알림을 보여주되 위치는 추정값이라고 표시한다.- 사용자가 기기 하나를 고르면
DELETE /v1/me/sessions/{session_id}가 소유권을 확인한다. 민감한 상황이면 재인증을 요구하고, 세션을revoked, 즉 철회됨으로 바꾸며 그 기기의 갱신 토큰 계보도 끊는다. POST /v1/me/logout-all은session_epoch를 올리고 모든 기기 세션과 갱신 계보를 철회한다.session_epoch는 계정에서 허용하는 최소 세션 세대를 나타내는 정수다. 접근 토큰의 세대가 이 값보다 낮은지 한 번 비교해 전체 로그아웃 이전 토큰을 가려내기 위해 둔다.- 서버는 철회 사건을 지역별 API 캐시와 연결 서비스로 보낸다. fan-out은 한 사건을 여러 수신처로 펼쳐 보내는 과정이며, 한 번 누른 로그아웃을 여러 서비스에 전달하기 위해 필요하다.
- 전달 실패는 재시도 큐에 남긴다. 이미 철회된 세션에 같은
DELETE가 다시 와도 성공으로 처리한다. 멱등성은 같은 요청을 여러 번 처리해도 최종 상태가 같은 성질이며, 네트워크 재시도가 중복 부작용을 만들지 않게 한다.
OIDC(OpenID Connect)는 한 로그인 결과를 여러 서비스가 함께 사용하는 표준이고, RP(Relying Party)는 그 결과를 받아 세션을 만든 서비스다. 이런 연합 환경에서는 Back-Channel Logout, 즉 브라우저를 거치지 않고 서버끼리 서명된 로그아웃 메시지를 보내는 방식으로 sid, 즉 세션 식별값에 해당하는 연결을 지울 수 있다. 이는 한 애플리케이션 내부의 모든 서비스에 반드시 써야 하는 규격은 아니다.
아래 그림은 왼쪽의 로그인에서 시작해 가운데의 토큰 회전을 거친 다음, 오른쪽에서 기기 로그아웃이 서버 기록·캐시·연결 서비스로 퍼지는 순서로 읽는다. 정상적인 동시 갱신과 탈취 재사용이 갈리는 지점, 전달 실패가 재시도로 넘어가는 경계도 함께 본다.

그림의 결론은 한 줄이다. 일반 API는 짧은 접근 토큰으로 빠르게 처리하지만, 새 토큰 발급과 로그아웃의 최종 판단은 서버의 기기 세션 기록이 한다.
데이터 모델은 기기 세션을 철회 단위로 묶는다
여기서 원장(authoritative record)은 세션이 유효한지 최종 판단하는 서버 기록을 뜻한다. 이 설계에서는 device_sessions와 토큰 계보 데이터가 원장이고, 캐시와 이벤트는 빠른 전달을 위한 복사본이다. 전파가 실패했을 때 무엇을 기준으로 복구할지 정하려면 이 구분이 필요하다.
| 테이블 | 핵심 필드 | 키·인덱스 | 보존 정책 |
|---|---|---|---|
users |
user_id, login_lookup, status, session_epoch, created_at |
기본 키 user_id, 고유 키 login_lookup |
탈퇴·법적 정책에 따라 분리 처리 |
password_credentials |
user_id, hash, algorithm, params, changed_at |
기본·외래 키 user_id |
로그인 성공 때 낡은 비용 설정을 새 해시로 교체 |
mfa_factors |
factor_id, user_id, type, secret_ref/public_key, status, bound_at |
기본 키 factor_id, 인덱스 (user_id,status) |
폐기 뒤 감사에 필요한 기간만 메타데이터 보존 |
device_sessions |
session_id, user_id, install_id_hash, epoch, status, created_at, last_seen_at, absolute_expires_at, risk_level |
기본 키 session_id, 인덱스 (user_id,status,last_seen_at) |
만료·철회 뒤 30일 보존은 설계 가정 |
refresh_tokens |
token_hash, session_id, family_id, parent_hash, status, issued_at, expires_at, used_at |
기본 키 token_hash, 인덱스 (family_id,status) |
원문은 저장하지 않고 절대 만료와 조사 기간까지만 계보 보존 |
auth_events |
event_id, user_id, session_id_hash, type, result, reason_code, ip_prefix, occurred_at |
날짜별 분할, 인덱스 (user_id,occurred_at) |
90일은 설계 가정이며 법과 위험 평가로 결정 |
revocation_outbox |
event_id, aggregate_id, version, payload, published_at, attempts |
기본 키 event_id, 미발행 사건 인덱스 |
전달 확인 뒤 짧게 보존 |
token_hash만 저장하면 데이터베이스가 유출돼도 원문 갱신 토큰을 바로 사용할 수 없다. 감사 기록에도 비밀번호, OTP, 원문 세션 ID와 토큰을 남기지 않는다. revocation_outbox는 세션 상태 변경과 전파할 사건을 같은 데이터베이스 처리에 기록하는 표다. 상태만 바뀌고 메시지가 사라지는 틈을 줄이기 위해 필요하다.
선택안은 수명이 짧은 서명 접근 토큰과 서버 기기 세션을 함께 쓰는 방식이다. 일반 API는 중앙 데이터베이스 없이 서명을 확인하고, 갱신과 기기 상태는 서버 기록이 결정한다. 결제나 비밀번호 변경 같은 민감 API는 캐시된 session_id + epoch 철회 상태까지 확인한다. 모든 API에 즉시 확인을 강제하지 않아 지연을 줄이면서 중요한 경로의 철회 시간을 좁힌다.
병목은 느린 해시와 잦은 갱신 쓰기에서 생긴다
- 비밀번호 해시: 공격자의 추측을 비싸게 만드는 계산이 로그인 서버에도 같은 비용을 준다. 제한된 전용 작업 풀과 대기열 상한을 두고, 운영 하드웨어의 부하 시험으로 비용 설정을 정한다. 부하가 높다고 보안 비용을 자동으로 낮추지는 않는다.
- 한 계정에 몰린 로그인: 추측 공격이 같은 사용자 행과 실패 카운터에 집중된다. 계정별·IP별 카운터를 분리하고 점증 대기와 추가 확인을 조합한다.
- 갱신 토큰 쓰기: 회전마다 데이터베이스 쓰기가 생긴다.
token_hash행의 조건부 변경과 기기별 직렬화가 필요하다. 여러 지역에서 동시에 쓴다면 어느 지역이 최종 결정을 내릴지 정해야 한다. - 기기 목록:
(user_id,status,last_seen_at)인덱스로 한 사용자의 세션만 읽는다. 모든 요청마다last_seen_at을 쓰지 않고 5분 단위로 합치는 것은 가능한 설계 가정이지만, 화면의 최근 시각이 그만큼 늦을 수 있다고 알려야 한다. - 철회 전파: 일반 사용자의 기기는 적지만 연결 서비스가 수백 개인 기업 계정에서는 느린 수신처 하나가 전체 완료를 늦춘다. 수신처별 제한 시간과 병렬 전송, 재시도, 미전달 경보가 필요하다.
- 감사 기록: append-only는 기존 사건을 덮어쓰지 않고 새 사건을 뒤에 추가하는 방식이다. 조사 흔적을 보존하기 위해 인증 요청과 분리해 기록하되, 전송 대기 사건이 쌓이면 경보한다.
사용자가 먼저 보는 실패가 복구 순서를 정한다
| 사용자에게 보이는 현상 | 감지 | 완화 | 복구와 같은 상태인지 확인 |
|---|---|---|---|
| 올바른 비밀번호인데 로그인이 느리다 | 해시 작업 대기와 p95·p99를 본다. p99는 요청 100개 중 가장 느린 한 개에 가까운 지연이다 | 작업 풀을 격리하고 대기열 상한을 둔다 | 과부하 해소 뒤 실제 사용자 없이 시험 계정으로 주기적으로 보내는 합성 로그인을 실행하고, 계정 존재를 드러내지 않는 재시도 안내를 확인한다 |
| MFA 번호가 중복·만료로 거부된다 | 도전 식별자의 상태와 만료, 소비 횟수를 본다 | TTL(Time To Live, 데이터가 유효한 시간)을 짧게 두고 한 번만 소비하며 시계 오차 범위를 둔다 | 새 도전을 발급하고 이전 도전이 다시 쓰이지 않는지 확인한다 |
| 정상 사용 중 갑자기 재로그인을 요구한다 | 토큰 재사용 사건과 동시 갱신의 시간·설치 정보를 함께 본다 | 같은 설치의 갱신을 직렬화하고 제한된 재시도를 적용한다 | 의심 계보를 끊고 새 세션이 옛 family_id와 분리됐는지 확인한다 |
| 기기 로그아웃 뒤에도 잠시 API가 열린다 | 철회 사건 지연, 캐시 버전, 접근 토큰 잔여 수명을 본다 | 민감 API가 철회 캐시를 확인하게 한다 | 사건을 재전송하고 캐시 버전이 같아졌는지, 만료된 옛 토큰이 거부되는지 시험한다 |
| 기기 목록의 위치·시각이 오래됐다 | last_seen_at 집계 지연과 위치 데이터 시각을 본다 |
마지막 확인 시각을 표시하고 위치 하나로 차단하지 않는다 | 원본 사건과 집계 진행 위치를 대조해 누락 구간을 다시 처리한다 |
| 전체 로그아웃 뒤 연결 서비스 하나가 남는다 | 수신처별 확인 응답과 재시도 횟수를 본다 | 다른 수신처와 격리해 재시도하고 로컬 세션부터 끊는다 | 서명된 사건을 다시 보내고 수신 서비스의 sid가 사라졌는지 확인한다 |
| 새 장소 알림이 잘못 울린다 | 위험 사유와 사용자 확인 결과를 기록한다 | 영구 차단보다 추가 인증과 알림을 먼저 쓴다 | 사용자의 확인을 이후 판단에 반영하되 감사 기록은 정한 기간 보존한다 |
공통 흐름은 사용자 증상과 지표로 감지 → 피해 범위를 줄여 완화 → 사건 재처리나 재인증으로 복구 → 데이터베이스·캐시·수신 서비스의 버전 확인 순서다. 재시도 큐가 비었다는 사실만으로 사용자의 세션이 실제로 끊겼다고 판단하지 않는다.
보안과 비용은 즉시 철회의 범위를 함께 결정한다
비밀번호에는 Argon2id의 비용 설정과 salt를 함께 저장한다. 선택적으로 쓰는 pepper는 모든 비밀번호에 더하는 별도 비밀값이며 데이터베이스 밖 비밀 저장소에 둔다. 데이터베이스만 유출됐을 때의 방어는 늘지만, pepper를 바꾸려면 기존 비밀번호를 알 수 없어 사용자 재설정이 필요할 수 있다.
MFA 수단을 바꿀 때는 기존 수단으로 다시 인증하고 다른 채널로 알린다. SMS나 TOTP는 도입하기 쉽지만 피싱 저항성이 아니므로 고위험 행동에는 WebAuthn 같은 암호학적 수단을 우선한다. 위험 기반 판단은 규칙과 설명 가능한 사유 코드로 시작한다. 오탐 이의제기와 학습 데이터가 충분하지 않은 상태에서 설명하기 어려운 모델부터 도입하면 차단 이유와 편향을 검증하기 어렵다.
세션 비밀은 인증 직후 새로 만들고 로그아웃 때 클라이언트와 서버 모두에서 무효화한다. 접근 토큰이 열 수 있는 API와 권한을 좁히고, 갱신 토큰에는 사용하지 않은 기간의 만료와 전체 수명의 절대 만료를 함께 둔다. 정확한 수명은 위험과 재로그인 비용을 비교해 정해야 한다.
IP 전체 값 대신 목적에 충분한 접두부나 파생 신호를 검토하고, 기기 식별 정보를 광고 추적에 재사용하지 않는다. 보존 기간과 알림 정책도 공개한다. 비용은 비밀번호 해시 연산, 여러 지역의 세션 복제, 감사 사건 보존, MFA 외부 전송과 계정 복구 지원에서 커진다. 즉시 철회를 모든 API로 넓힐수록 매 요청 조회 비용과 인증 시스템 장애에 함께 묶이는 범위도 커진다.
대안은 철회 속도와 운영 복잡도를 맞바꾼다
| 결정 | 선택안 | 대안과 포기한 이유 | 선택을 바꿀 조건 |
|---|---|---|---|
| 세션 표현 | 짧은 서명 접근 토큰 + 서버 기기 세션 | 순수 서버 세션은 매 요청 의존성이 커지고, 장기 JWT(JSON Web Token, 서버 서명으로 내용을 검증하는 토큰)는 즉시 철회가 약하다 | 규모가 작고 위험이 높으면 매 요청 서버가 확인하는 불투명 세션을 우선한다 |
| 갱신 보호 | 기기별 회전 계보 + 재사용 시 계보 철회 | 고정 갱신 토큰은 탈취된 옛 값의 재사용을 찾기 어렵다 | 기기가 가진 별도 암호 키로 토큰 사용자를 증명하는 방식이 널리 지원되면 함께 쓴다 |
| 로그아웃 전파 | 서버 원본 즉시 변경 + 비동기 사건 + 짧은 접근 토큰 | 모든 수신처의 동기 완료를 기다리면 느린 서비스가 사용자 응답을 막는다 | 규제나 피해 규모상 즉시 차단이 필요하면 민감 API의 중앙 확인 범위를 넓힌다 |
| 위험 인증 | 설명 가능한 규칙과 사유 코드 | 초기부터 복잡한 예측 모델을 쓰면 오탐과 편향을 설명하기 어렵다 | 충분한 정답 데이터와 이의제기 기록이 쌓이면 통제 비교 뒤 확장한다 |
| MFA | 위험·권한에 따라 WebAuthn 우선, TOTP를 복구 대안으로 둔다 | SMS만 쓰면 번호 탈취와 피싱 위험이 있고, 모든 사용자에게 강제하면 이탈과 복구 비용이 커진다 | 서비스 위협과 사용자 기기 지원률에 맞춰 단계화한다 |
RFC(Request for Comments)는 인터넷 기술의 표준과 모범 관행을 기록한 문서다. RFC 9700의 갱신 토큰 회전 원칙은 OAuth 2.0, 즉 사용자가 비밀번호를 직접 넘기지 않고 외부 앱에 제한된 권한을 주는 표준의 공개 클라이언트를 대상으로 한다. 이 글은 그 재사용 탐지 원리를 자체 로그인 세션에도 적용한 설계 판단이지, 모든 로그인 시스템이 같은 토큰 형식을 써야 한다는 뜻은 아니다. RFC 7009도 철회를 요구하면서 분산 서버 사이의 전파 지연 가능성을 인정한다. 따라서 로그아웃 API의 200 OK를 모든 API가 같은 순간 거부한다는 보장으로 확대해서는 안 된다.
확장은 실제 철회 지연이 복잡성을 요구할 때 시작한다
- 초기: 단일 지역의 관계형 데이터베이스, 보호 쿠키 기반 불투명 세션, Argon2id, 기본 MFA, 기기 목록과 기기별 철회로 시작한다. 감사 사건은 요청 처리와 분리해 남긴다.
- 성장: 짧은 접근 토큰과 서버 갱신 세션을 나누고, 회전 계보·재사용 감지·철회 캐시를 넣는다. 최근 사용 시각 쓰기를 합치고 비밀번호 해시 작업을 격리한다.
- 여러 지역: 사용자나 세션별로 쓰기를 담당할 홈 지역을 정해 회전 충돌을 막는다. 철회 사건에는 계속 증가하는 버전을 붙이고 각 지역이 마지막으로 적용한 버전과 지연을 공개한다.
- 연결 서비스 확대: OIDC Back-Channel Logout, 수신처별 재시도 상태판과 서명 키 교체를 도입한다. 브라우저에만 의존하는 로그아웃은 모바일 앱과 브라우저 개인정보 제한에서 충분하지 않을 수 있다.
- 고위험 서비스: 피싱 저항성 MFA, 민감 API의 지속적인 세션 확인, 기기 키와 묶은 토큰, 개인정보 영향 평가를 강화한다. 철회 지연·탈취 재사용·오탐 지표가 정한 한계를 넘을 때 다음 단계로 간다.
선택한 설계는 빠른 일반 요청을 위한 짧은 접근 토큰과 철회 가능한 기기별 서버 세션을 결합한다. 남는 한계는 접근 토큰 만료 전의 오래된 권한, 분산 수신처의 전파 지연, 정상 동시 갱신과 탈취 재사용을 완벽히 구별할 수 없다는 점이다. 실제 운영에서는 사용자의 로그인 경험보다 앞서 복잡한 분산 구성을 사지 말고, 자신의 서비스에서 허용할 로그아웃 전파 지연과 동시 기기 정책을 수치로 정한 뒤 그 목표를 깨는 지표부터 측정해야 한다.
참고 링크
- NIST SP 800-63B Authentication and Lifecycle Management
- RFC 9700: Best Current Practice for OAuth 2.0 Security
- RFC 7009: OAuth 2.0 Token Revocation
- OpenID Connect Back-Channel Logout 1.0
- OWASP Password Storage Cheat Sheet
- OWASP Session Management Cheat Sheet
- OWASP Multifactor Authentication Cheat Sheet