登录后的页面被共享缓存存了一份,缓存头里的私有与不可存该怎么写

📅 2026-10-11 👁️ 1

登录后的页面被共享缓存存了一份,问题通常出在缓存头写得不完整。按 MDN 的定义,no-store 表示任何类型的缓存(私有或共享)都不应存储该响应;private 表示响应只能存于私有缓存,也就是浏览器本地缓存。两者的区别不是「缓存多久」,而是「允许谁存」。带登录态的页面只写过期时间而不写归属,共享缓存就完全可能把那一份响应复用给后面的其他用户。

两类缓存分别是谁

私有缓存位于使用者一侧,典型是浏览器本地缓存,天然按用户隔离,存了个性化内容通常不构成泄露。

共享缓存位于源站与客户端之间,例如反向代理、CDN 节点。MDN 的描述很清楚:它存一份响应并复用给多个用户。这正是风险的来源——共享缓存不理解登录态,它只按缓存键判断是不是同一个请求。

还有一类容易被忽略:应用内部的响应缓存与页面静态化产物。它们的性质介于两者之间,取决于生成时机是否包含用户身份。凡是「生成时知道当前用户、输出时不再区分用户」的缓存,都应按共享缓存对待。

不可存与仅私有各自含义

两者的适用边界要分清,写错方向比不写更麻烦。

指令 语义 适用对象 误用后果
no-store 任何缓存都不得存该响应 含凭据、交易、个人数据的页面 加了它,性能优化完全无从谈起
private 只允许私有缓存存 因人而异但敏感性不高的页面 若下游仍强缓存,等于没生效
public + 过期时间 允许共享缓存存 与身份无关的公开内容 个性化页面写错会串给用户

选择顺序建议这样问:这个响应里有没有只属于当前用户的信息?如果有且不能容忍被他人读到,用 no-store。如果内容因人而异但敏感性有限、且缓存能带来明显收益,用 private。如果与身份无关,才可以进共享缓存。

一个常见误解是「private 会让 CDN 不缓存」。更准确的说法是:private 是要求共享缓存不要存它,但一些中间层的行为并不完全遵循该语义。因此对真正敏感的响应,no-store 才是确定性更强的写法。

会员分组页面为什么更容易出问题

站内支持用户管理和 VIP 分组,可以为不同用户组设置访问权限。这类分层能力一旦与缓存相遇,就会形成典型的串页问题:同一个地址,因登录用户所属用户组不同而返回不同内容,但缓存键里没有体现这个差异。

三类页面最容易被漏掉。一是登录后显示的会员专区入口,不同分组看到的内容不同;二是价格、下单与订单相关页面,权限决定可见范围;三是下载或附件类页面,权限校验在应用层,而响应一旦进了共享缓存,后续的访问就不再触发校验。

第三种尤其危险:它给人的错觉是「我程序里做了权限判断,所以安全」。但缓存会跳过程序,权限判断根本没被执行。

判断标准应当是「响应是否因人而异」,而不是「页面是否重要」。凡是渲染时读过用户身份、分组或权限的响应,都默认排除在共享缓存之外,再按需要单独放开与身份无关的子资源。

反向代理与加速节点上的副作用

链路上多一层缓存,就多一份风险,因为规则分散在两处:应用写响应头,中间层按自己的策略决定是否遵循。站点部署时如果同时存在反向代理与加速节点,这两层要一起核对,只调整应用侧常常不够。

要核对四件事。第一,中间层是否有强制缓存策略。有些配置会覆盖上游指令,此时应用侧写什么都没用。第二,命中缓存时响应头是否被改写。若缓存后头信息丢失,从客户端看不出这份内容来自缓存。第三,退出登录后缓存里的旧响应还在不在。登录态变化不会自动失效共享缓存中的对象。第四,静态化产物是否按用户区分目录。把登录页写成静态文件,是同类问题里最难被发现的一种。

三步自查

第一步,用两个不同身份的账号访问同一地址,比较响应是否真的不同。若第二次访问拿到了第一次的内容,说明命中了不该有的共享缓存。

第二步,看响应头里的归属声明与缓存标识,确认这条响应被哪一层存过。缺失声明的响应要补,带有缓存命中痕迹的登录态响应要立刻处理。

第三步,检查退出登录路径。可靠的实现应当让登录后的跳转地址不进入共享缓存,而不是依赖用户端清理。

常见问题

全站统一加 no-store 可以吗? 技术上可以,但会把公开内容的缓存收益一起牺牲,站点负载明显上升。应按响应类型区分,而不是全局一刀切。

只在登录接口加够吗? 不够。问题出在渲染了登录态的页面响应上,接口只是其中一部分。

CDN 上的 private 会不会生效? 不同实现的行为不完全一致,敏感响应用 no-store 更确定。上线前应在真实节点上验证一次,而不是依赖文档描述。

静态化怎么做才安全? 与身份无关的页面才静态化;会员相关内容改为按权限判断动态渲染,或者把差异收敛到不缓存的小块区域。

怎么判断有没有历史响应残留? 观察退出登录后是否仍能访问到旧内容,或核对缓存层的对象存在时间。发现残留时先关闭该路径的缓存,再排查生成逻辑。

相关文章

上传目录的类型头写对了还不行,禁止内容嗅探的响应头为什么要单独加

MDN 文档写明 X-Content-Type-Options 表示 Content-Type 里声明的类型应当被遵守而不应被更改,nosniff 会在脚本或样式请求的类型不符时阻止响应。本文说明嗅探发生在哪一步、上传目录为什么要在部署层再挡一道,以及站内两层各管什么。

2026-10-11

HTTP/3 要在 Web 服务器里打开,端口、证书和编译选项各要动哪一处

Nginx 官方模块文档写明 ngx_http_v3_module 自 1.25.0 起提供实验性 HTTP/3 支持且默认不编译,需要 --with-http_v3_module 启用,示例监听端口建议与 HTTPS 一致,0-RTT 还要求 OpenSSL 3.5.1 或更高。本文按编译、监听、证书、库版本四步说明启用顺序。

2026-10-11

开源协议写明是 GPL 时,改版和交付给客户要注意什么

以 WordPress 官方授权页为依据,GPLv2 或更新版本的要求覆盖衍生作品,插件与主题被官方视为衍生作品,同时存在法律灰区。本文说明对外交付时要带的三样东西、多站点与二次开发的分界,以及选型阶段的授权核对顺序。

2026-10-11

列表页图片换成新一代压缩格式,体积和抓取会一起变吗

官方文档给出的压缩读数是:有损新一代格式相比 JPEG 平均小约 50%,另一种常见格式平均小 25%—35%,无损场景约小 26%;同时明确建议保留回退格式。本文说明换格式后体积会降,但抓取读到什么取决于图片清单与替代文本,并给出站内落地顺序。

2026-10-11

按请求头协商语言的站点加了缓存,译文为什么会发给用另一种语言的人

MDN 的 Vary 文档写明该响应头描述方法与 URL 之外影响响应内容的请求信息,包含 Vary 可确保响应依据所列首部字段分别被缓存,最常见用途是内容协商时构造缓存键。本文解释译文串给别的访客的成因、响应该怎么声明,以及多语言两种排法的差别。

2026-10-11

站点升级时挂维护页返回 503,为什么比返回空白页更稳妥

MDN 的 503 文档写明该状态码表示服务器未准备好处理请求,常见原因是维护或过载,响应应仅用于临时状况并尽可能带上预计恢复时间的 Retry-After 首部;文档同时提示 503 表示临时问题,通常不应缓存。本文说明维护窗口里状态码、重试时间与缓存该怎么配。

2026-10-11

页面其实存在却一直返回 404,收录会受到什么影响

内容还在库里,前台却把页面判成不存在,搜索引擎收到的信号是资源消失,收录与流量都会跟着掉。本文说明统一错误码为什么危险,状态码按语义分流的几种情况,以及文档状态、伪静态规则、重定向和站点地图四处配置的排查顺序。

2026-10-11

登录成功后的跳转地址由参数带来,不校验会被拿去做什么

登录回跳参数是外部可控输入,不做集中校验时,一个合法的登录流程会被改造成可信域到钓鱼页的跳板。本文说明这类跳转地址会被拿去做什么、白名单该挡哪几类取值,以及验证码与频率控制在登录链路上的分工。

2026-10-11