尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
cookie、sessionStorage、localStorage 存储选型与安全边界详解
有一次帮同事排查问题一个后台报表页面用户前一天晚上把筛选条件、表格列配置都调好了第二天一早打开系统全部回到默认状态。同事第一反应是“cookie被清了”我打开控制台看了下存储发现他用的 sessionStorage标签页一关数据自然是人间蒸发。cookie、sessionStorage、localStorage 这三兄弟的区别几乎每个前端人都背过但真到了项目里能一次选对的人真不多。这篇文章我不打算给你背概念而是从实际问题出发把三者的底层逻辑、作用域边界、安全风险、选型判断一次讲透。不管你是刚入行的前端还是要接手中大型项目的维护者哪怕是准备面试看完这篇应该都不会再被问倒。1. 从一次“数据莫名丢失”事故说起——三个存储的相处之道1.1 一个页面里同时存在三种存储选错就会“背锅”你有没有遇到过这样的场景用户在页面上填了很长一段表单不小心点了个刷新内容全没了。你第一反应是“应该用 localStorage 啊”结果代码里写的是 sessionStorage刷新是保留了但用户把标签页关了再打开还是没了。还有一种情况更隐蔽登录之后跳转到另一个页面token 在 A 页面能拿到到 B 页面就变成 null。排查半天发现写入时用的是 sessionStorage而 B 页面是用户手动新开标签页访问的根本拿不到 A 标签页里的数据。这类问题有一个共同点存储本身没坏是选型选错了。cookie、sessionStorage、localStorage这三个 API 都存在于浏览器里但它们的生命周期、作用域、容量、传输方式完全不同。不理解这些差异光靠背“cookie 小、storage 大、session 关页就没”这种口诀到了真实项目里还是会踩坑。所以这篇我先从底层讲起。你只有弄清楚它们各自“从哪来、归谁管、活多久”才能在任何场景下做出正确的选择。1.2 协议产物和浏览器 API本质就不在同一个体系中先说一句可能颠覆你认知的话cookie 根本不是浏览器发明的它是 HTTP 协议的组成部分。HTTP 协议是无状态的服务器无法记住“你是谁”。为了解决这个问题早期引入了 cookie 机制服务器通过响应头Set-Cookie在浏览器里种下一个凭证浏览器以后每次请求都把这个凭证放进Cookie请求头里带回给服务器。所以 cookie 的生命周期、大小限制、作用域规则很多都是从协议层面定死的。而 sessionStorage 和 localStorage 不一样它们是 HTML5 提供的 Web Storage API是浏览器主动提供的“本地仓库”。它们不参与 HTTP 请求的自动发送纯粹是给 JavaScript 在浏览器端读写数据用的。打个比方cookie 像是你出门必须挂在胸口的工牌走到哪个门禁都要被扫描一下随请求自动发送而 Web Storage 像是你办公桌抽屉里的笔记本方便自己查阅但路过门口时门卫不会自动翻你的笔记本。这个底层差异决定了后面所有区别的走向为什么 cookie 只有 4KB 左右而 storage 可以到 5MB因为 cookie 每次请求都要跟着头走太大直接浪费带宽为什么 storage 不需要设置过期时间而 cookie 需要因为 storage 本来就是一个“本地文件”浏览器不主动清理它就一直放着。2. cookie身背重担的协议老兵为什么还没退休2.1 cookie 的真实工作流程从 Set-Cookie 到请求头你在浏览器控制台里执行document.cookie namevalue这只是在当前域名下设置了一个 cookie。但完整的 cookie 机制尤其是服务端设置、HttpOnly、Secure 这些属性很多人并没有亲自抓包看过。我建议你做一个实验打开浏览器的开发者工具随便访问一个需要登录的网站切到 Network 面板点开任意一个同域请求查看请求头里的Cookie字段再看看响应头里的Set-Cookie字段。你会发现服务端返回的 Set-Cookie 往往带有Path/; HttpOnly; SameSiteLax之类的属性。这些属性不是摆设HttpOnly意思是这个 cookie 不允许被document.cookie读取只能随请求自动发送目的就是防 XSS 偷取身份凭证。Secure表示只允许在 HTTPS 连接下传输。SameSite则是用来限制跨站请求时是否携带 cookie 的可以在一定程度上防止 CSRF。我见过不少新人把 token 直接写进document.cookie既不设 HttpOnly 也没有 Secure结果站点被注入了一段脚本后身份凭证被人轻松拿走。正确做法是敏感凭证尽量让服务端设置 HttpOnly cookie前端代码永远不应该能读到它。如果前端必须持有 token那也至少要保证传输层是 HTTPS并且不要放在内存之外的地方长期保存。2.2 决定 cookie 上限的三个维度大小、数量、域名cookie 的大小并不是一个完全统一的值不同浏览器实现有细微差别但业界公认的经验值大约是4KB。什么概念一条 cookie 存 2000 个汉字就顶天了。这里说的 4KB 是整个域名下的所有 cookie 总和不是单条。如果你往同一个域名下塞了 50 条 cookie每条约 80 字节很快就到极限了。除了大小还有数量限制。早期规范建议每个域名下最多 20 个 cookie现代浏览器大多是 50 个左右。超出的部分浏览器会默默丢弃而你的代码根本不会报错这种“静默失败”是排查问题时最头疼的。更麻烦的是cookie 存储在浏览器后会以字符串形式拼接到每个请求的Cookie头里。想象一下你的业务代码往 cookie 里塞了一堆用户偏好、浏览记录、购物车数据。用户每次点击、每次刷新、每次异步请求这些数据都要跟着请求头来回传输。请求头被撑大了传输变慢服务器解析也要多花时间。我的铁律是cookie 只存“必要凭证”比如 session ID、用户 ID、CSRF token其他能不进 cookie 的坚决不进。2.3 cookie 的路径、域名和失效机制坑多但必须懂cookie 的作用域规则比 storage 复杂得多因为它在协议层面支持Domain和Path属性。Domain决定哪些域名可以读取这个 cookie。如果你在a.example.com下设置一个 cookie 指定Domainexample.com那么b.example.com也能拿到。注意设置 Cookie 时 Domain 不能设为当前域之外的域不是你想设谁就能设谁的这是浏览器的同源保护之一。Path决定同一域名下哪些路径可以访问。默认情况下Path/表示整站可用。如果你设置Path/admin那只有/admin及其子路径能读到这个 cookie。失效机制也值得细说。不设置Expires或Max-Age时cookie 是会话级 cookie关掉浏览器就没了。设置了之后就变成持久化 cookie到期前一直存在。这里有个容易踩坑的点Expires接收的是 HTTP 时间格式如果你直接用 JavaScript 的new Date()往 Set-Cookie 里拼格式经常对不上。优先使用Max-Age它以秒为单位没有格式解析问题而且它和Expires同时存在时Max-Age优先级更高。我还遇到过一种情况明明调用了document.cookie token; expiresThu, 01 Jan 1970 00:00:00 GMT但 cookie 还是没删掉。原因是删除时Path和Domain必须和设置时完全一致。设置时用了Path/app删除时没写Path结果就删不掉。这是个很经典的坑。3. sessionStorage 与 localStorage双胞胎的两种宿命3.1 写法几乎一样命运却完全不同从使用者的角度看sessionStorage 和 localStorage 简直是一对双胞胎同样的 APIsetItem、getItem、removeItem、clear、key连存储格式都是键值对字符串。很多人因此觉得它们“差不多”只是在“关闭标签页会不会清空”上有所不同。但如果只记住这一点你会在更隐蔽的地方踩坑。先说共性两者都只能存储字符串。你要存对象必须自己JSON.stringify转成字符串读出来再JSON.parse解析回对象。如果你直接setItem(obj, {a: 1})存进去的会是[object Object]。另外它们的容量大约是5MB 或 10MB不同浏览器有差异远超 cookie 的 4KB但也远小于后端数据库。再说差异sessionStorage 的生命周期绑定标签页。什么概念不是“关闭浏览器”而是关闭标签页。你在地址栏输入同一个网址回车打开一个新标签页和原来的标签页是完全隔离的。如果你用window.open(url)打开新标签页只要新页面是通过脚本弹出的它会复制一份 sessionStorage 过来但两者从此互不相干。localStorage 则简单粗暴没有过期时间没有标签页概念同源下的所有页面共享同一份数据除非你手动清掉或代码删除否则它会一直霸占空间。3.2 sessionStorage 的作用域陷阱标签页隔离sessionStorage 最容易被低估的是它的“标签页隔离”属性。很多人以为它是“会话级”的觉得浏览器没关就一直能用。实际上它的生命周期是“标签页级”。举个例子你的系统有个列表页点进去是详情页。用户在列表页设置了一组筛选条件存到 sessionStorage然后跳到详情页。此时因为详情页是同一个标签页内通过路由跳转的sessionStorage 可以读到。但如果用户从详情页复制链接再开一个新标签页访问列表页那筛选条件是读不到的。因为这是一个全新的标签页存的是另一份空数据。iframe 也是类似的情况。如果页面里有 iframe 加载同源页面它们可以共享 sessionStorage但跨域 iframe 就不行。虽然这个场景不多但碰到了也够你排查一阵子。sessionStorage 最适合的场景是那些“开了一个标签页就要用关了就拉倒”的临时数据比如表单草稿用户填到一半刷新页面草稿还在用户彻底关闭页面草稿自动清理不用你操心过期时间。3.3 localStorage 的持久性既是福利也是坑localStorage 能永久保存这既是它的优势也埋着不少隐患。第一个隐患是“数据结构老化”。你的业务代码可能在一期版本里往 localStorage 里存了{version: 1, data: ...}到了三期版本代码里改成读{version: 2, items: [...]}。老用户浏览器里还留着一期的旧数据页面一打开就解析报错。这时候你得写一套迁移逻辑或者至少要在读写时做结构校验。第二个隐患是“空间滥用”。localStorage 虽然单个域名有 5MB 左右的额度但如果你什么数据都往里扔——操作日志、埋点队列、图片 base64、接口缓存——用着用着就到顶了。而且 localStorage 的写入是同步的往里面塞大字符串时页面甚至会卡顿。我见过一个运营后台每次搜索完都把整个结果列表存进 localStorage列表几千条数据存一次要几百毫秒页面明显卡顿。第三个隐患是“清理无门”。用户清浏览器缓存时一般会连 localStorage 一起清掉你的“永久登录”状态可能就会丢。如果你把重要的 token 存在 localStorage用户清除浏览器数据后就会被踢下线这是很多“为什么我清了缓存还要重新登录”问题的根源之一。我有一个习惯给 localStorage 里的每类数据加一个前缀并存入版本号。比如myapp:user_pref:v1。这样后续升级、清理、调试都能方便很多。4. 三个维度的碰撞生命周期、容量与作用域到底差在哪4.1 一句话记住三者的区别到这一步我们可以把三者的核心差异放在一起看维度cookiesessionStoragelocalStorage本质HTTP 协议产物浏览器本地存储 API浏览器本地存储 API存储大小约 4KB域名总限额约 5MB 以上约 5MB 以上生命周期可设置 Expires/Max-Age不设则为会话级标签页关闭即销毁永久保存需手动清理传输方式每次 HTTP 请求自动携带不随请求发送不随请求发送作用域按域名 路径可跨子域同源 同标签页同源协议域名端口默认隔离按域名标签页间隔离同源共享可被服务端设置是Set-Cookie否否这个表格建议你存下来面试和平时遇到选型问题先过一遍这个表基本不会错。4.2 同源的三位一体协议域名端口Web Storage 的作用域“同源”到底怎么理解“源”是由三部分组成的协议、域名、端口。任何一项不同都不算同源都不能共享 localStorage。举个实际的坑你的站点在http://example.com下运行用户把 http 访问习惯改成手动输入https://example.com此时浏览器会认为http和https是两个完全不同的源。localStorage里的数据在新的源下面是空的页面上的“记住我”功能就失效了。如果你同时接入了 HTTP 和 HTTPS 两套环境要格外注意 storage 的隔离问题。子域名也是重灾区。假设admin.example.com和www.example.com是两个应用它们各自访问各自的localStorage互不共享。如果你的业务需要主站和子站之间共享登录态那通常还是得靠 cookie因为 cookie 可以指定Domainexample.com让所有子域名都能读到。代价是这个 cookie 也会被发送到所有子域名的请求里安全边界变宽了。记住一句话localStorage 是同源共享sessionStorage 是同源同标签页共享cookie 是“域 路径”共享。三者作用域的范围并不相同。4.3 什么时候选哪个先回答三个问题与其背“cookie 用来存 sessionstorage 用来存缓存”不如在每次决定用哪个存储之前先问自己三个问题问题一这个数据需要发给服务端吗如果需要cookie 是天然候选因为它是唯一能随 HTTP 请求自动携带的。但注意不要存大体积数据只放凭证类信息。如果服务端只需要在特定时机拿到数据那也可以用 storage 存前端手动放到请求体里发送。问题二关闭标签页之后这个数据还需要存在吗不需要选 sessionStorage需要选 localStorage 或持久化 cookie。问题的关键就是这个页面是“用完即走”还是“长期复用”。问题三需要多个页面共享吗在一个标签页内路由跳转之间共享sessionStorage 就够跨标签页共享localStorage 才是正解跨子域共享只能靠 cookie。这三个问题走完90% 的选型场景都有答案了。5. 被低估的安全边界XSS、CSRF 与存储选择的攻防关系5.1 cookie 是 CSRF 的天然靶子很多初学者会困惑cookie 这么不方便为什么不用 localStorage 存一切“每次请求都自动带上”这个特性是把双刃剑。它带来了便利也带来了 CSRF跨站请求伪造风险。简单说CSRF 的攻击方式是你在 A 网站已登录浏览器里存着 A 网站的登录 cookie。你又在另一个网站上点了某个恶意链接这个链接指向 A 网站的某个敏感接口。由于 cookie 会被浏览器自动携带A 网站收到请求后会认为这是你本人操作。要防御 CSRF单纯靠存储选型是不够的它需要后端配合。常见做法有校验Referer、使用自定义请求头、使用 CSRF token。现代浏览器也提供了SameSite属性设置为Lax或Strict后跨站请求就不会自动携带 cookie。这也是为什么现在越来越多的网站在设置登录 cookie 时都会加上SameSiteLax。所以在选型时你要有个意识cookie 作为凭证载体的前提是服务端必须有能力防御 CSRF。如果你们的接口主要是内部管理系统且不打算做复杂的 CSRF 防护那可以考虑把 token 放到 localStorage 里由前端在请求头里手动加Authorization字段。这样至少不会因为“cookie 自动携带”就产生跨站漏洞。5.2 localStorage 和 XSS 的裙带关系localStorage 不是没有安全问题它最大的软肋是 XSS跨站脚本攻击。XSS 攻击如果得手攻击者可以在你的页面里执行任意 JavaScript。这时候localStorage 里的所有内容都像摆在桌面上的文件一样可以随便翻阅。如果你的 token、用户信息放在 localStorage攻击者一条fetch就能把它们偷走。相比之下cookie 有一个 localStorage 没有的保命技能HttpOnly。设置了 HttpOnly 的 cookie 不能被document.cookie读取所以即使页面被注入脚本攻击者也偷不走这个 cookie。这就是为什么“token 到底放 cookie 还是 localStorage”会争论这么久。放 cookie你有 CSRF 问题放 localStorage你有 XSS 风险。在我看来不存在绝对安全的选项只有匹配你防御体系的选项如果你们的接口有完善的 CSRF 防护且登录 cookie 设置了 HttpOnly SameSite那 cookie 更稳。如果你们是前后端分离、以 Authorization 请求头传递 token 的架构并且代码里对 XSS 有专门防护那 localStorage 也能用但要定期清理、加过期时间。最怕的是“人云亦云”网上说 localStorage 不安全就一律不碰或者图省事全扔 localStorage。安全是个系统工程存储选型只是中间一环。5.3 不同存储的安全等级与取舍攻击类型cookiesessionStoragelocalStorageXSS 偷取可由 HttpOnly 缓解可被脚本读取可被脚本读取CSRF 利用受 SameSite 和 CSRF 防护影响不自动发送风险低不自动发送风险低数据被第三方脚本读取看是否有 HttpOnly同源页面脚本均可读同源页面脚本均可读清除浏览器数据后丢失是是是有一个容易被忽视的隐患是第三方脚本。现在很多站点会用统计脚本、客服插件、埋点 SDK这些第三方脚本在页面里执行时同样可以访问 localStorage 和 sessionStorage只要它们在同一个 Document 下。所以不要把敏感信息随便存进 storage尤其是你没有完整审计过第三方依赖的项目。我在项目里养成的一个习惯是storage 里可以放 UI 状态、用户偏好、非敏感缓存但不放密码、不放长期 token。如果必须放 token就设一个短的过期时间并且每次读取时校验签发时间超过阈值强制重新登录。6. 面试与实战都能用的选型清单6.1 60秒速记表如果面试官让你现场说区别你可以按这个节奏答从本质讲cookie 是 HTTP 协议的一部分自动随请求发送Web Storage 是浏览器 API纯本地存储。从容量讲cookie 约 4KBstorage 约 5MB 起。从生命周期讲cookie 由服务端或 JS 通过 Expires/Max-Age 控制sessionStorage 关标签页即销毁localStorage 永久保存。从作用域讲cookie 可按 Domain 跨子域、按 Path 限定路径storage 受同源限制其中 sessionStorage 还多一层标签页隔离。按这四条答下来比死背概念要稳得多因为“为什么”的逻辑是自洽的。6.2 一份可以直接抄的 Storage 封装你如果经常在项目里直接调 localStorage我建议你花 10 分钟封装一个模块。下面这个是我常用的结构带命名空间、JSON 序列化和过期时间const STORAGE_PREFIX myapp; function buildKey(key) { return ${STORAGE_PREFIX}:${key}; } export const storage { set(key, value, maxAgeSeconds) { const data { v: 1, value, expiresAt: maxAgeSeconds ? Date.now() maxAgeSeconds * 1000 : null }; localStorage.setItem(buildKey(key), JSON.stringify(data)); }, get(key) { const raw localStorage.getItem(buildKey(key)); if (!raw) return null; try { const data JSON.parse(raw); if (data.expiresAt Date.now() data.expiresAt) { localStorage.removeItem(buildKey(key)); return null; } return data.value; } catch (e) { // 结构损坏时兜底清理避免页面报错 localStorage.removeItem(buildKey(key)); return null; } }, remove(key) { localStorage.removeItem(buildKey(key)); }, clear() { // 只清理自己前缀下的 key避免误删其他模块数据 Object.keys(localStorage) .filter((key) key.startsWith(STORAGE_PREFIX)) .forEach((key) localStorage.removeItem(key)); } };这段封装解决了我说的几个痛点带前缀避免和其他功能冲突带版本字段方便以后迁移带过期时间强制清理。你如果对 sessionStorage 也有类似需求把localStorage换成sessionStorage即可。6.3 我的实战建议清单项目里具体的选型我一般按下面的规则来你可以参考登录态和身份凭证优先交给服务端设置 HttpOnly cookie并配置 SameSite。如果架构上必须前端持有 token也存在内存变量里别长期落盘。用户偏好设置主题、语言、列表视图localStorage。这类数据不敏感需要跨页面长期保留。表单草稿sessionStorage。用户关闭标签页后草稿就该消失免得下次进来还看到旧内容。跨标签页通信localStorage 加storage事件。注意这个事件在其他标签页修改数据时才会触发当前标签页不会触发需要自己处理。接口缓存localStorage 可以但记得设过期时间、控制体积最好再加上容量上限检查超过阈值就直接不缓存。什么场景不该用哪类存储也顺便提醒一下不要在 cookie 里存业务数据请求头会膨胀。不要把整个用户对象塞进 localStorage真要存就存一个不敏感的 ID其他信息每次从接口拉。不要在一个标签页里给 sessionStorage 存大量数据因为新开标签页并不会自动带过去。我自己维护的项目里凡是存储选型没想清楚的过一阵子一定会有人过来说“数据莫名其妙没了”或者“为什么这个页面不共享数据”。这些问题的根源基本都能在这篇文章里找到答案。最后再分享一个调试小技巧控制台的 Application 面板或者旧版叫 Resources里可以分门别类地查看 Cookie、Local Storage、Session Storage。遇到数据丢失问题第一时间去这里看一眼比在代码里瞎找一个小时靠谱得多。存储这块你理解得越透彻排查问题就越从容。
RELATED

相关推荐

智能穿戴APP开发避坑指南:如何挑选靠谱服务商

智能穿戴APP开发避坑指南:如何挑选靠谱服务商

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/18 1:07:01
ESP32+RC522零基础跑通RFID刷卡读卡全流程

ESP32+RC522零基础跑通RFID刷卡读卡全流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/18 1:07:01
协处理器系统集成实战:从总线拓扑到Linux驱动的完整方法论

协处理器系统集成实战:从总线拓扑到Linux驱动的完整方法论

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/18 1:07:01
MORE NEWS

更多资讯

📰

OpenProject 开源项目管理软件:社区版免费自部署,任务与甘特图排期完整指南

OpenProject 开源项目管理软件:社区版免费自部署,任务与甘特图排期完整指南 【免费下载链接】openproject OpenProject is the leading open source project management software for product, project and portfolio management. A powerful Jira alter…

📰

树莓派桌面系统登陆PC与Mac:轻量Linux发行版深度体验

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

UNIHH-IOT:轻量级物联网平台的高性能实现原理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

TWS耳机芯片选型指南:11家原厂26款方案深度解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

你的表征学得再“准”,真的是“因果”而不是“碰巧相关”吗?

全文目录:开篇语前言1) 研究问题:我们到底想让表征“泛化”到什么程度?2) 背景与意义:为什么深度表征特别爱学“相关性捷径”?3) 文献与缺口:理论很多,能跑又能扩展的框架还不够“顺手”4) 方法…

📰

Breeze TTS 2部署指南:Docker镜像、A100/H100适配与显存需求全解析

【免费下载链接】Breeze-TTS-2 项目地址: https://ai.gitcode.com/hf_mirrors/BreezeBlue/Breeze-TTS-2 点击查看 免费下载 Breeze TTS 2 是一款面向实时交互的开源权重文本转语音(Text-to-Speech)模型。本指南带你完成 Breeze TTS 2 部署全…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬