Кэш nginx, который не должен видеть страницу с логином
Микрокэш HTML на 10 секунд в shared memory дёшев. Он же ломается, как только на запросе есть auth_token. Обход по cookie, не по догадке.
Vue SSR стоит за nginx. После деплоя анонимный GET страницы заметок это один и тот же HTML десять секунд. Я положил это в fastcgi_cache с зоной ключей на 8 МБ. CPU Node упал. Потом чужой человек получил мою админскую оболочку на эти десять секунд, потому что ключ кэша был только URL.
Sanctum держит сессию в httpOnly cookie auth_token. HTML залогиненного это не закрытое тело статьи. Это другая шапка, другое меню, другое пустое состояние. Одной закэшированной страницы с этой вёрсткой хватает. nginx отдал её как HIT следующему анониму.
Пользователи публичного сайта увидели хром редактора, которого видеть не должны. Я увидел кэш, который не отличает запрос с cookie от публичного.
Обход по cookie
Правило в nginx одна строка: если на запросе есть auth_token, кэш не трогать. То же для CSRF-cookie на POST. /api я не кэширую вообще. JSON из LaraBoom и так в Redis, когда это стоит, а закэшированный 401 хуже медленного 401.
Зона живёт в воркере nginx, не в Redis. В этом смысл. Redis уже держит payload списков. nginx держит готовую оболочку. Смешать их значит, что сброс кэша должен ходить в два хранилища, и одно я забуду.
На location заметок я логировал $upstream_cache_status. После логина тот же URL должен был показать MISS или BYPASS, не HIT. HIT с моим именем в теле значил, что ключ по-прежнему игнорирует cookie. Vary на Cookie разрезал бы зону на запись на сессию. Bypass дешевле и частный HTML не хранит совсем.
Что мерить
fastcgi_cache_bypass и fastcgi_no_cache нужны с одним условием. Bypass на чтении, no_cache на записи. Если выставлено только одно, ответ залогиненного всё равно попадает в зону, и следующий аноним может его получить. Это и был баг: bypass был, no_cache нет, и моя оболочка записала публичный ключ.
После логина бью тот же URL дважды, с cookie и без. Первый должен быть miss и уйти в Node. Второй, без cookie, может быть HIT. Если в HIT-теле есть моё имя, bypass неправильный.
TTL оставляю 10 секунд. Дольше публикация из админки сидит за nginx, когда Redis уже истёк. Короче зона не работает. Кэш ещё пропускаю на всё, что не GET. HEAD от зонда не должен наполнять HTML. Query string публичного списка заметок входит в ключ только если меняет страницу. Tracking-параметры не должны.
Какой опыт из этого
Меряю статус кэша с auth_token и без на одном URL. HIT с именем на экране это утечка, даже за 10 секунд.
Ключу кэша из одного пути я не верю. Bypass и no_cache должны делить условие по cookie. Vary на Cookie складывает частные страницы. Зону лучше обойти.
Правило, которое оставляю: запрос с cookie никогда не пишет публичный HTML-ключ. JSON остаётся у PHP и Redis. nginx кэширует только анонимные GET-оболочки.
