다른 도메인으로 옮기면 가끔 로그아웃 상태로 보이던 이유 — location / 에 프록시 캐시가 살아 있었다
문제 발생
로그인한 채 서브도메인으로 옮기면 가끔 로그아웃 상태로 보였습니다. 쿠키 설정도 시크릿도 다섯 앱이 같았고, 새로고침하면 대개 돌아왔습니다. "간혹"이라는 것이 단서였습니다.
nginx 설정을 보니 /dashboard와 /api/에는 proxy_no_cache와 proxy_cache_bypass가 있었는데, 정작 대부분의 페이지가 지나가는 location /에는 아무것도 없었습니다. 캐시 존은 이 저장소에 없는 http {} 블록에서 켜져 있었습니다.
원인 분석
캐시는 상속됩니다. proxy_cache는 http, server, location 어디서든 선언할 수 있고 아래 블록이 물려받습니다. 상위에서 켰다면 location /도 캐시 대상이고, 그 응답이 어떤 것인지는 nginx가 모릅니다.
이 문서들은 헤더에 로그인한 사람의 닉네임과 로그아웃 버튼이 들어가는 세션에 따라 달라지는 HTML입니다. 캐시에 들어가면 로그아웃 상태로 저장된 응답이 로그인한 사람에게 나가거나, 그 반대가 됩니다. 캐시 항목이 있을 때만, 그 도메인을 옮길 때만 생기니 "간혹"으로 보입니다.
앱이 보내는 헤더만 믿을 수 없습니다. nginx 문서에 따르면 proxy_ignore_headers로 Cache-Control 처리를 끌 수 있습니다. 그 한 줄이 http {}에 있으면 앱이 아무리 no-store를 보내도 저장됩니다. 우리 저장소 밖의 설정 하나에 로그인 상태의 안전이 걸려 있으면 안 됩니다.
해결 방안
- 세션이 섞이는 location에는
proxy_cache off를 명시합니다. 문서 그대로,off는 이전 설정 수준에서 상속된 캐싱을 비활성화합니다. 정적 자산처럼 캐시해도 되는 경로만 따로 열어 둡니다.
location / {
proxy_pass http://127.0.0.1:3000;
proxy_cache off;
}
location /_next/static/ {
proxy_pass http://127.0.0.1:3000;
proxy_cache app_cache;
}- 앱도 같은 말을 합니다. MDN 기준으로
no-store는 어떤 캐시도 이 응답을 저장하지 말라는 뜻이고,private은 브라우저 같은 개인 캐시에만 저장할 수 있다는 뜻입니다. 로그인 뒤 받는 개인화된 응답에는private을 붙이라고 MDN이 직접 권합니다. 회사망 프록시처럼 우리 nginx 밖의 캐시에는 이 헤더가 유일한 방어입니다.
Cache-Control: private, no-store
Vary: CookieVary: Cookie는 만약 어딘가에서 저장되더라도 쿠키가 다른 요청에 같은 응답을 주지 말라는 표시입니다.
-
proxy_no_cache만으로는 부족합니다. 그 지시자는 조건이 참일 때만 저장을 막습니다. 조건 변수가 비어 있는 경로에서는 그대로 저장됩니다. 세션 화면에서는 조건이 아니라off가 맞습니다. -
진단 스크립트를 둡니다. 다섯 호스트에 로그인 쿠키를 넣고 요청해
X-Cache-Status(캐시 존에add_header로$upstream_cache_status를 노출한 경우)가HIT인 곳이 있는지 봅니다. 제보를 받으면 추측하기 전에 이것을 먼저 돌립니다. -
Cache-Control을 앱 프레임워크가 정하는 경우가 있습니다. 동적 페이지의 헤더를 프레임워크가 덮어쓰면 우리가 원하는 값이 나가지 않을 수 있습니다. 실제 응답 헤더를curl -I로 확인한 뒤 nginx에서proxy_hide_header와add_header로 맞춥니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.