2-3. 쿠키와 상태 — 무상태 프로토콜 위의 로그인
한 줄 요약: 쿠키의 저장·첨부 규칙과 보안 속성의 함의를 이해하고, "왜 이 요청에 쿠키가 안 실리는가"를 진단하며 세션과 토큰의 트레이드오프를 판단할 수 있다.
학습 목표
Set-Cookie가 지시일 뿐이며 저장·첨부의 결정권이 브라우저에 있다는 동작 모델을 설명할 수 있다.Domain/Path/만료 속성이 결정하는 쿠키 전송 조건을 정확히 판정할 수 있다 — 특히Domain명시 여부가 뒤집는 서브도메인 규칙.Secure/HttpOnly/SameSite각각이 어떤 위협을 막고 무엇을 못 막는지 구분할 수 있다.- 세션 식별자 방식과 자체 포함 토큰(JWT)의 트레이드오프를 무효화·크기·저장소 축으로 비교하고 상황에 맞게 선택할 수 있다.
- "서버가 Set-Cookie를 보냈는데 동작하지 않는" 상황을 DevTools로 진단할 수 있다.
배경: 왜 이것이 존재하는가
HTTP는 무상태(stateless)다 — 각 요청은 이전 요청에 대한 기억 없이 독립적으로 처리된다. 이것은 결함이 아니라 설계다. 서버가 클라이언트별 상태를 유지할 필요가 없으므로 아무 서버나 아무 요청을 처리할 수 있고(수평 확장), 중간자(2-1)가 요청을 독립 단위로 캐시·재시도할 수 있다. 하지만 "로그인"이라는 요구는 정확히 그 반대 — 연속된 요청들이 같은 사용자의 것임을 아는 것 — 를 필요로 한다.
쿠키(RFC 6265)는 이 간극을 메우는 표준 메커니즘이다: 서버가 응답에 작은 데이터를 실어 보내면, 브라우저가 저장했다가 이후 조건에 맞는 모든 요청에 자동으로 되돌려 보낸다. 상태 자체는 여전히 어딘가(서버 세션 저장소든 토큰 내부든)에 있고, 쿠키는 그 상태를 가리키는 운반 수단이다.
백엔드 경험자에게 이 영역은 절반만 익숙하다. 세션 미들웨어를 설정하고 req.session을 읽어 봤다면 서버 쪽 절반은 아는 것이다. 이 문서가 다루는 것은 나머지 절반 — 미들웨어가 내보낸 Set-Cookie를 받은 브라우저가 무엇을 하는가다. 이 절반이 어려운 이유는 실패가 조용하기 때문이다. 브라우저가 쿠키를 거부하거나 첨부하지 않기로 결정해도, 그 결정은 어떤 에러 응답으로도 돌아오지 않는다. 서버는 그저 "쿠키 없는 요청"을 받을 뿐이고, 개발자는 로그인이 안 되는 이유를 서버 코드에서 찾아 헤매게 된다.
쿠키 보안이 얽히는 공격(XSS, CSRF)의 벡터와 방어 체계는 Phase 7-4에서 정면으로 다룬다. 여기서는 쿠키 자체의 동작 모델과 각 속성이 만드는 경계까지를 다룬다.
핵심 개념
동작 모델 — 결정권은 브라우저에 있다
쿠키의 생애는 세 단계다.
① 서버 → 브라우저: Set-Cookie: sid=abc123; Path=/; HttpOnly; Secure
(지시 — "저장해 달라")
② 브라우저: 속성을 검증하고 쿠키 저장소(cookie jar)에 저장 (또는 조용히 폐기)
③ 브라우저 → 서버: Cookie: sid=abc123
(이후 조건에 맞는 모든 요청에 자동 첨부)강조할 것은 ②와 ③의 주어가 전부 브라우저라는 점이다. 서버는 저장을 요청할 수 있을 뿐 강제할 수 없고, 저장소를 조회할 수도 없다. 브라우저가 쿠키를 거부하는 경우(속성 규칙 위반, 크기 초과, 서드파티 차단 정책)에도 응답은 이미 처리된 뒤라 서버에 알려줄 채널 자체가 없다. 또한 ③의 첨부는 자동이다 — 애플리케이션 코드가 개입하지 않아도, 조건에 맞으면 <img> 태그의 이미지 요청에도, 폼 제출에도 실린다. 이 자동성이 쿠키의 편리함이자 (CSRF라는) 보안 문제의 근원인데, 그 이야기는 SameSite에서 이어진다.
curl로 전 과정을 재현할 수 있다.
# -c: 받은 Set-Cookie를 파일(쿠키 저장소 역할)에 저장
curl -sc /tmp/jar.txt https://httpbin.org/cookies/set/sid/abc123 -o /dev/null
cat /tmp/jar.txt
# httpbin.org FALSE / TRUE 0 sid abc123 ← 도메인·경로·만료가 함께 기록된다
# -b: 저장소에서 조건에 맞는 쿠키를 자동 첨부
curl -sb /tmp/jar.txt https://httpbin.org/cookies
# {"cookies": {"sid": "abc123"}} ← 서버가 받은 Cookie 헤더범위 규칙 — Domain 명시가 규칙을 뒤집는다
쿠키가 어느 요청에 첨부되는지는 저장 시 기록된 범위 속성이 결정한다.
Domain생략 (host-only 쿠키): 쿠키를 설정한 정확히 그 호스트에만 첨부된다.example.com이 설정하면api.example.com에는 가지 않는다.Domain=example.com명시: 그 도메인과 모든 서브도메인에 첨부된다. 직관과 반대로, 명시하는 쪽이 범위가 넓다. "명시 = 서브도메인 공유 의도의 선언"으로 읽어야 한다.- 자신과 무관한 도메인이나 상위의 공개 접미사(public suffix)로는 설정할 수 없다.
foo.github.io가Domain=github.io로 설정하는 것은 브라우저가 거부한다 —github.io가 Public Suffix List에 등재되어 있어 서로 다른 사용자의 사이트 간 쿠키 공유가 차단되기 때문이다. Path: 요청 경로가 이 값으로 시작할 때만 첨부. 단, 보안 경계가 아니다 — 같은 오리진의 다른 경로 문서가 JS로 우회할 수 있으므로 트래픽 절약용 필터 정도로만 취급한다.- 만료:
Max-Age(초) 또는Expires(절대 시각,Max-Age우선). 둘 다 없으면 세션 쿠키 — 브라우저 세션 종료 시 삭제된다. 다만 요즘 브라우저는 "세션 복원" 기능으로 세션 쿠키를 되살리는 일이 흔해서, 세션 쿠키를 "브라우저 닫으면 확실히 사라진다"는 보안 장치로 믿으면 안 된다.
전형적 함정: 로그인은 example.com에서 처리하고 API는 api.example.com에 있는 구조에서, 세션 쿠키를 Domain 없이 설정하면 host-only가 되어 API 요청에 쿠키가 실리지 않는다. 의도적으로 Domain=example.com을 명시해야 서브도메인 간 공유가 된다 — 그리고 그 순간 모든 서브도메인(운영하지 않는 것 포함)이 이 쿠키를 읽게 된다는 트레이드오프도 함께 온다.
보안 속성 — 각각이 막는 것과 못 막는 것
| 속성 | 막는 것 | 못 막는 것 |
|---|---|---|
Secure | 평문 HTTP로의 쿠키 전송(도청) | HTTPS 페이지 안의 다른 모든 위협 |
HttpOnly | document.cookie를 통한 JS 접근 — XSS로 쿠키 값 탈취 | XSS 자체. 주입된 스크립트가 쿠키를 못 읽어도, 그 브라우저에서 요청을 보내면 쿠키는 자동 첨부되므로 세션을 도용한 행동은 가능하다 |
SameSite | 크로스 사이트 요청에의 자동 첨부 — CSRF의 주 벡터 | 같은 사이트 안에서의 공격, XSS |
SameSite는 셋 중 가장 최근에 추가됐고 가장 파급이 컸다.
Strict— 크로스 사이트 요청에는 절대 첨부하지 않는다. 다른 사이트에서 링크를 클릭해 들어오는 최상위 내비게이션에도 첨부하지 않으므로, 메일의 링크로 서비스에 들어온 사용자가 로그아웃 상태로 보이는 부작용이 있다.Lax— 크로스 사이트라도 최상위 내비게이션 + 안전한 메서드(GET 링크 클릭 등)에는 첨부한다. 링크 유입은 로그인 유지되고, 크로스 사이트 POST(전형적 CSRF)나 이미지·iframe 로드에는 첨부하지 않는 절충.None— 항상 첨부. 반드시Secure와 함께여야만 유효하다.
2020년부터 Chrome은 SameSite 미지정 쿠키를 Lax로 취급하기 시작했다(이전 기본은 사실상 None). 기본값 변경의 이유는 CSRF를 옵트인 방어에서 기본 방어로 바꾸기 위해서다 — 크로스 사이트 요청에 쿠키가 자동으로 실리는 것이 CSRF의 전제 조건인데, 그 전제를 플랫폼 차원에서 제거한 것이다. 이 변경으로 한 시대의 통합들이 일제히 깨졌다: 외부 결제 페이지에서 POST로 복귀하는 플로우, 크로스 사이트 iframe 안의 위젯, OAuth 콜백 일부. "어느 날 갑자기 결제 후 돌아오면 로그아웃되는" 장애의 원인이 대부분 이것이었고, 해법은 해당 쿠키에 명시적 SameSite=None; Secure 지정이다. 참고로 Firefox·Safari의 기본 동작은 Chrome과 완전히 같지 않으므로(Safari는 자체 추적 방지 정책이 우선) 크로스 사이트 쿠키가 필요한 기능은 브라우저별 확인이 필요하다.
__Host- 접두사 — 이름으로 강제하는 불변 조건
쿠키에는 미묘한 신뢰 문제가 있다: 서버는 받은 쿠키가 어떤 속성으로, 누가 설정했는지 알 수 없다. Cookie: sid=abc123에는 속성 정보가 없다. 그래서 evil.example.com(서브도메인 하나가 탈취됐다고 하자)이 Domain=example.com으로 같은 이름의 쿠키를 심으면, 본 사이트는 자신이 설정한 쿠키와 구별할 수 없다 — 세션 고정(session fixation) 계열 공격의 토대다.
__Host- 접두사는 이 문제를 이름 규약으로 푼다. 브라우저는 __Host-로 시작하는 쿠키를 Secure + Domain 없음(host-only) + Path=/ 조건을 만족할 때만 저장한다. 따라서 서버가 Cookie: __Host-sid=...를 받았다면, 이 쿠키는 정확히 이 호스트가 HTTPS로 설정한 것임이 구조적으로 보장된다. 서브도메인도, 평문 연결도 이 이름의 쿠키를 만들 수 없기 때문이다. 완화판인 __Secure- 접두사는 Secure만 강제한다. 새로 설계하는 세션 쿠키라면 __Host- 접두사가 기본값이어야 한다 — 서브도메인 공유가 필요한 경우만 예외다(그 경우 __Host-는 쓸 수 없다).
세션 식별자 vs 자체 포함 토큰
쿠키에 무엇을 담을 것인가에는 두 계열이 있다. 상태의 위치가 갈린다.
| 축 | 세션 식별자 (서버 세션) | 자체 포함 토큰 (JWT 등) |
|---|---|---|
| 쿠키 내용 | 무의미한 난수 키 | 서명된 클레임 덩어리 (사용자 ID, 권한, 만료…) |
| 상태 위치 | 서버 저장소 (Redis, DB) | 토큰 안 (서버는 서명만 검증) |
| 요청당 서버 비용 | 저장소 조회 1회 | 서명 검증 (조회 없음) |
| 즉시 무효화 | 가능 — 저장소에서 삭제하면 끝 | 불가능 — 만료까지 유효. 차단 목록을 만들면 결국 저장소 조회가 부활한다 |
| 크기 | 수십 바이트 | 수백 바이트~KB 단위 — 매 요청에 실린다 |
| 수평 확장 | 저장소 공유 필요 | 서버 무상태 — 검증 키만 배포 |
| 담긴 정보의 신선도 | 조회 시점 기준 최신 | 발급 시점 기준 — 권한 변경이 만료까지 반영 안 됨 |
트레이드오프의 축은 명확하다: JWT는 요청당 저장소 조회를 없애는 대가로 즉시 무효화와 정보 신선도를 포기한다. "로그아웃하면 그 세션은 즉시 죽어야 한다", "권한 회수는 즉시 반영돼야 한다"가 요구사항이라면 JWT 단독으로는 충족할 수 없고, 차단 목록이나 짧은 만료 + 리프레시 토큰 같은 보완 장치가 붙는다 — 그런데 보완 장치가 붙는 순간 "조회 없는 인증"이라는 원래의 이점이 상당 부분 상쇄된다. 서비스 규모가 세션 저장소 조회를 감당할 수 있다면(대부분 그렇다) 세션 방식이 더 단순하고 통제력이 높다. JWT가 명확히 이기는 지형은 발급자와 검증자가 다른 조직/서비스인 경우(마이크로서비스 간, 제3자 API)다. 토큰을 쿠키에 둘 것인가 localStorage에 둘 것인가의 보안 논쟁은 Phase 7-4에서 다룬다.
쿠키의 비용 — 모든 요청에 실리는 바이트
쿠키의 자동 첨부는 요청 선택권이 없다는 뜻이기도 하다. 조건에 맞으면 HTML 문서 요청에도, 이미지 300장에도, 폰트에도 전부 실린다. 쿠키를 2KB 쌓아 둔 도메인에서 페이지가 리소스 100개를 로드하면 업로드만 200KB다 — 업링크가 느린 모바일에서 체감되는 크기이고, 요청 헤더는 압축 전이라면 매번 온전히 전송된다.
이 비용이 HTTP/1.1 시대에 "정적 자산은 쿠키 없는 별도 도메인(cookieless domain)에서 서빙"하는 관행을 낳았다. 현재는 두 가지가 상황을 바꿨다: HTTP/2의 HPACK 헤더 압축이 반복 전송되는 쿠키를 사실상 1회 전송으로 만들었고(2-4), 캐시 파티셔닝(2-2)으로 도메인 분리의 다른 이점도 줄었다. 그래도 원칙은 남는다 — 쿠키는 명시된 범위의 모든 요청에 실리는 데이터이므로, 크기와 Path/Domain 범위를 필요 최소로 유지한다. 쿠키 하나의 크기 제한은 약 4KB(이름+값 기준, RFC 6265의 최소 요구치)이고 도메인당 개수 제한(50~180개, 브라우저마다 다름)도 있다.
서드파티 쿠키 — 닫히고 있는 문
지금까지의 논의는 사용자가 방문 중인 사이트의 쿠키(퍼스트파티)였다. 페이지에 삽입된 다른 오리진의 리소스(iframe 위젯, 광고, 분석 스크립트) 요청에 실리는 그 오리진의 쿠키가 서드파티 쿠키다. 메커니즘은 동일하지만 용도가 문제였다 — 광고 네트워크가 여러 사이트에 삽입되면, 같은 쿠키로 사용자의 사이트 간 이동을 이어 붙일 수 있다(크로스 사이트 추적).
브라우저별 현황은 갈라져 있다(2026년 기준): Safari(ITP)와 Firefox(ETP)는 수년 전부터 기본 차단이고, Chrome은 단계적 폐기를 수년간 추진하다 2024년에 전면 폐기 계획을 철회하고 제한적 유지로 선회했다. 결과적으로 "서드파티 쿠키가 동작한다"는 가정은 이미 브라우저 과반에서 깨져 있다. 정당한 용도(임베드 위젯의 자체 세션 등)를 위한 표준 대안이 CHIPS(Partitioned 속성) — 서드파티 쿠키를 허용하되 삽입된 최상위 사이트별로 분리 저장해서, A 사이트의 위젯 쿠키와 B 사이트의 위젯 쿠키가 서로 다른 쿠키가 되게 한다. 추적은 차단하고 기능은 보존하는 절충이다. 임베드형 제품(위젯, 로그인 iframe)을 만든다면 이 지형 변화가 설계 전제가 된다.
실무 관점
"Set-Cookie를 보냈는데 로그인이 안 된다" 진단표
실패가 조용하므로 진단 절차를 갖춰야 한다. DevTools Network 패널에서 해당 응답의 Set-Cookie 헤더에 노란 경고 아이콘이 뜨면, 마우스를 올려 거부 사유를 직접 확인할 수 있다 — 이것이 첫 번째 확인 지점이다.
| 증상 | 원인 후보 |
|---|---|
| 저장 자체가 안 됨 | Secure 쿠키를 HTTP에서 설정 / SameSite=None인데 Secure 누락 / __Host- 규칙 위반 / 크로스 사이트 응답(fetch)의 쿠키가 서드파티 정책으로 차단 |
| 저장은 됐는데 특정 요청에 안 실림 | host-only 쿠키인데 서브도메인 API 호출 / Path 불일치 / 크로스 사이트 요청 + SameSite=Lax 이상 / fetch에 credentials 옵션 누락(CORS 상황 — Phase 7-2) |
| 어느 날 갑자기 깨짐 | 브라우저 기본값 변경(SameSite Lax화, 서드파티 차단 강화) — 코드는 그대로인데 플랫폼이 움직인 경우 |
Application 패널 → Cookies에서 저장된 쿠키의 실제 속성(Domain 앞의 점 유무 = host-only 여부, SameSite 열)을 확인하는 것까지가 기본 절차다.
설계 기본형
새로 세션 쿠키를 설계한다면 출발점은 이것이다.
Set-Cookie: __Host-sid=<난수>; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=86400여기서 무언가를 완화할 때마다 이유가 있어야 한다: 서브도메인 공유가 필요해서 __Host-를 포기하고 Domain을 명시한다든가, 크로스 사이트 임베드가 필요해서 SameSite=None(+CSRF 대책 별도 마련, 7-4)으로 간다든가. 기본형에서 출발해 요구사항이 완화를 강제하는 방향이, 열린 설정에서 출발해 조이는 방향보다 안전하다.
JWT 만능론에 대한 경계
"세션은 구식, JWT가 현대적"이라는 통념이 있지만, 위 트레이드오프 표가 보여주듯 JWT는 무효화와 신선도를 지불하고 조회를 없애는 특정한 거래다. 단일 서비스 + 세션 저장소를 감당할 수 있는 규모에서 JWT를 도입하면, 대가는 그대로 지불하면서 이점(조회 제거)은 필요하지 않았던 경우가 된다. 실제로 많은 팀이 JWT 도입 후 "강제 로그아웃" 요구사항을 만나 차단 목록을 만들며 세션 저장소를 재발명한다. 기술 선택의 근거를 "무엇이 현대적인가"가 아니라 "이 거래가 지금 필요한가"에 두는 것 — 이 커리큘럼 전체의 원칙이 그대로 적용되는 사례다.
더 깊이
스펙의 이중 구조 — RFC 6265와 6265bis
쿠키 스펙의 역사는 특이하다. 쿠키는 1994년 Netscape가 만들어 스펙 없이 퍼졌고, 사후에 표준화 시도(RFC 2109, 2965)가 있었지만 현실의 브라우저들이 따르지 않아 실패했다. 현행 RFC 6265(2011)는 반대로 "이상적 설계"를 포기하고 이미 배포된 브라우저 동작을 기술하는 방식으로 성립했다 — 그래서 스펙 안에 서버 요구사항과 별개로 "사용자 에이전트 요구사항" 장이 있고, 파싱 규칙이 관대하고 지저분하다(HTML 파서의 오류 복구와 같은 계열의 역사다 — Phase 1-1). SameSite, 접두사, Partitioned 등 이후의 확장을 통합한 개정판이 RFC 6265bis라는 이름으로 오랫동안 드래프트 상태로 진행 중이며, 브라우저들은 RFC 완성을 기다리지 않고 드래프트를 구현해 배포한다. "스펙이 현실을 따라가는" 웹 표준의 전형적 동학이다.
Cookie 헤더에는 속성이 없다 — 정보 소실의 함의
브라우저가 요청에 싣는 것은 Cookie: name=value; name2=value2뿐이다. 서버는 이 쿠키의 Domain이 무엇이었는지, Secure였는지, 언제 만료되는지 알 수 없다. 같은 이름의 쿠키가 다른 범위로 여러 개 존재하면(host-only sid와 Domain=example.com의 sid) 둘 다 전송될 수 있고, 순서 규칙(더 구체적인 Path 우선)은 있지만 신뢰할 만한 구분 수단이 아니다. __Host- 접두사가 "이름에 속성 보장을 인코딩"하는 우회로였다는 것이 이 맥락에서 다시 보인다. 새 API인 CHIPS가 속성 이름을 Partitioned로 붙인 것도 같은 제약 안에서의 설계다.
쿠키 저장소의 물리적 실체
쿠키 저장소는 추상이 아니라 파일이다 — Chrome은 프로필 디렉터리의 SQLite DB에 쿠키를 저장한다(값은 OS 키체인 연동으로 암호화). HttpOnly가 막는 것은 페이지 JS의 API 접근이지 디스크상의 존재가 아니므로, 로컬 머신을 장악한 공격자에게는 의미가 없다. 보안 속성들의 위협 모델이 어디까지인지(네트워크 도청, 페이지 내 스크립트까지이고 로컬 침해는 범위 밖) 구분해 두면, 각 속성에 과잉 기대를 걸지 않게 된다.
정리
- 쿠키의 저장과 첨부는 전적으로 브라우저의 결정이고, 거부는 조용하다. 진단은 서버 코드가 아니라 DevTools(Set-Cookie 경고, Application 패널)에서 시작한다.
Domain생략은 host-only(그 호스트만), 명시는 서브도메인 전체 공유 — 직관과 반대다.Path는 보안 경계가 아니다.Secure/HttpOnly/SameSite는 각각 도청, XSS를 통한 값 탈취, CSRF형 자동 첨부라는 서로 다른 위협을 막는 독립 장치이며,SameSite=Lax기본화는 CSRF 방어를 옵트인에서 기본으로 바꾼 플랫폼 차원의 결정이었다.- 새 세션 쿠키의 기본형은
__Host-접두사 +Secure+HttpOnly+SameSite=Lax이고, 완화에는 각각 이유가 필요하다. - 세션 vs JWT는 "요청당 저장소 조회를 없애는 대가로 즉시 무효화와 클레임 신선도를 포기하는 거래"다. 그 거래가 필요한 규모·구조인지가 판단 기준이고, 쿠키는 어느 쪽이든 그것을 나르는 수단일 뿐이다.
확인 문제
1. www.example.com에서 로그인하면 서버가 Set-Cookie: sid=abc; Path=/; Secure; HttpOnly를 응답한다. 이후 프론트엔드가 api.example.com으로 요청을 보내는데 서버 로그에는 쿠키가 비어 있다. 원인과 두 가지 해결 방향은? 각 방향의 트레이드오프도 말하라.
정답과 해설
Domain 속성이 없으므로 이 쿠키는 host-only — 정확히 www.example.com으로 가는 요청에만 첨부되고 api.example.com에는 실리지 않는다. 해결 방향: (1) Domain=example.com을 명시해 서브도메인 전체 공유로 만든다. 트레이드오프 — 운영하지 않는 서브도메인을 포함한 모든 서브도메인이 쿠키를 받게 되고, __Host- 접두사를 쓸 수 없게 되며, 서브도메인 하나가 탈취되면 쿠키 주입 표면이 된다. (2) API를 같은 호스트 아래 경로로 옮긴다(www.example.com/api — 리버스 프록시나 BFF 구성). 트레이드오프 — 인프라 구성 변경이 필요하지만 쿠키는 host-only + __Host-를 유지할 수 있어 보안 경계가 가장 좁다. (CORS와 credentials 이슈도 함께 사라진다는 부수 이점이 있는데, 상세는 Phase 7-2.)
2. 외부 결제 대행 사이트로 이동했다가 결제 완료 후 POST로 우리 사이트에 복귀하는 플로우가 있다. 어느 시점부터 복귀 직후 사용자가 로그아웃 상태로 보이는 버그가 보고됐다. 코드 변경은 없었다. 무슨 일이 일어났고, 올바른 수정은 무엇인가? SameSite=Strict로 바꾸는 것은 왜 답이 아닌가?
정답과 해설
세션 쿠키에 SameSite가 미지정이었고, Chrome이 미지정 쿠키를 Lax로 취급하기 시작하면서(2020년 이후 순차 적용) 동작이 바뀐 것이다. 결제 사이트에서의 복귀는 크로스 사이트 + POST이므로 Lax 쿠키가 첨부되지 않는다 — 복귀 요청에 세션 쿠키가 없으니 서버는 비로그인으로 처리한다. 수정: 이 플로우에 필요한 쿠키를 SameSite=None; Secure로 명시한다(그리고 None으로 여는 만큼 CSRF 방어를 별도로 갖춘다 — Phase 7-4). 또는 플로우 자체를 바꿔 복귀를 GET 최상위 내비게이션으로 만들면 Lax로도 첨부된다(결제 결과는 서버 간 웹훅으로 받는 설계). Strict는 반대 방향이다 — Lax보다 더 조여서 크로스 사이트 GET 내비게이션에도 쿠키를 빼므로, 문제를 악화시킨다.
3. 팀이 "서버를 무상태로 만들자"며 세션을 JWT(만료 24시간)로 교체했다. 배포 후 보안팀에서 "탈취 신고된 계정을 즉시 차단하라"는 요구사항이 왔다. 팀은 무효화된 토큰 ID를 Redis에 저장하고 매 요청마다 확인하는 방식을 구현했다. 이 시점에서 원래 목표는 어떻게 되었는지 평가하고, 대안 설계를 제시하라.
정답과 해설
원래 목표("요청당 저장소 조회 제거 = 무상태")는 사실상 소멸했다 — 차단 목록 확인이 매 요청의 Redis 조회를 부활시켰기 때문이다. 결과물은 "세션 저장소보다 통제력은 낮고(토큰의 클레임은 여전히 발급 시점 기준), 조회는 그대로인" 구성으로, 세션 방식 대비 이점이 없다. 대안: (1) 규모가 세션 저장소를 감당한다면 세션 방식으로 회귀하는 것이 가장 단순하다 — 즉시 무효화가 자연스럽게 해결된다. (2) JWT를 유지해야 한다면(예: 다수 서비스가 독립 검증) 액세스 토큰 만료를 수 분으로 짧게 하고 리프레시 토큰으로 갱신하는 구조로 — 무효화는 리프레시 시점에만 확인하므로 "즉시"가 아니라 "수 분 내" 차단이 되고, 이 지연이 보안 요구사항과 합의 가능한지가 관건이다. 어느 쪽이든 핵심 교훈: 즉시 무효화 요구사항이 있는 시스템에서 순수 자체 포함 토큰은 성립하지 않으며, 이 요구사항 유무를 기술 선택 전에 확인해야 한다.
참고 자료
- RFC 6265 — HTTP State Management Mechanism — 현행 쿠키 스펙. 서버 요구사항(§4)과 브라우저 요구사항(§5)의 분리 구조 자체가 읽을거리다.
- RFC 6265bis (IETF draft) — SameSite, 접두사 등 현대 쿠키의 실질적 스펙. 브라우저들이 실제로 구현하는 기준.
- MDN — Using HTTP cookies — 속성별 동작과 브라우저 차이의 실무형 정리.
- web.dev — SameSite cookies explained — Lax 기본화의 배경과 마이그레이션 가이드.
- CHIPS — Cookies Having Independent Partitioned State — 서드파티 쿠키 이후의 파티셔닝 모델.
- Public Suffix List —
Domain설정이 거부되는 경계의 실체. 쿠키 외에도 여러 브라우저 보안 경계가 이 목록에 의존한다.