大家好,我是大华! 前几天有个小伙伴问我:"我登录之后拿到了token,到底该往哪儿存?LocalStorage、SessionStorage还是Cookie?为啥不同网站做法不一样?" 说实话,这个问题我也曾经纠结过好久!每次看到不同项目用不同的存储方式,我就想问:到底哪个是对的?
前言
为什么token存储这么重要?想象一下,你家的钥匙你会放哪儿?随身携带?藏在门垫下面?还是交给保安?放错了地方,小偷就可能进你家门!
token就是用户进入系统的钥匙,存错了地方,黑客就能冒充用户登录账号,后果不堪设想啊!
一、区别
1. LocalStorage
永久存储(除非手动删)
同源就能读(JS随便拿)
刷新不丢,关浏览器也不丢
XSS攻击下,Token直接暴露
需要手动添加到请求头
2. SessionStorage
只在当前会话有效
同源可读
适合临时操作
关了浏览器标签就没了
同样有XSS风险
需要手动管理
3. Cookie
可设置过期时间
可设置HttpOnly(JS拿不到!)
可设置Secure(只走HTTPS)
可设置SameSite(防CSRF)
自动随请求发送(比如发API时自动带Token)
容量限制(4KB)
每次请求都携带(可能浪费流量)
4. 内存存储(Memory)
页面刷新就丢失
完全前端控制,不持久化
最快最安全,但生命周期最短
页面刷新就丢失
不适合持久化需求
标签页关闭就没了
二、为什么大家都用LocalStorage?
我懂,很多前端的朋友第一反应:"用LocalStorage最方便啊!"
两行代码搞定:
js
复制代码
// 存
localStorage.setItem('token', res.token);
// 发请求时
axios.defaults.headers.common['Authorization'] = localStorage.getItem('token');
方便、简单是真的,但同时也会伴随着安全隐患。
案例1:XSS攻击,Token被偷
假设你网站有个评论区,用户输入没做转义:
html
复制代码
fetch('/steal?token=' + localStorage.getItem('token'))
用户打开页面,这条JS一执行,你的Token就会被发送到黑客服务器上。 而如果 Token 在HttpOnly Cookie里,JS 读不到,XSS 攻击直接失效。
三、用Cookie就完美了吗?
不,Cookie也有坑。黑客诱导你访问一个恶意页面:
html
复制代码
如果你的登录态在 Cookie 里,浏览器会自动带上 Cookie,请求就成功了!
用户没点确认,钱就没了。
解决方案
方案一:前后端分离 + JWT + LocalStorage(最常见)
这是目前绝大多数新项目采用的方式。
技术栈:
前端:Vue3 + Vue CLI / Vite,部署在 Nginx / CDN
后端:SpringBoot,提供 RESTful API
通信:Axios + JWT(JSON Web Token)
Token存储 :localStorage或内存
部署方式:
bash
复制代码
用户浏览器
↓
Vue 前端(http://fe.yourcompany.com) ←→ SpringBoot 后端(http://api.yourcompany.com)
前端和后端完全独立部署,通过CORS跨域通信。
认证流程:
用户登录
SpringBoot验证用户名密码,生成JWT
返回给前端:{ token: "xxxxxx" }
前端存入 localStorage
后续请求,前端手动加Header:
js
复制代码
axios.defaults.headers.common['Authorization'] = 'Bearer ' + token
SpringBoot在拦截器中解析JWT,验证身份
优点:
前后端完全解耦,各自独立开发、部署、扩展
适合微服务、云原生架构
开发简单,调试方便
可配合Nginx做负载均衡、缓存
缺点:
XSS 风险高 :一旦有富文本漏洞,localStorage中的token可能被盗
需要手动管理token(过期、刷新)
跨域配置麻烦(CORS)
📌 适用场景:
中后台管理系统
ToC产品(官网、商城)
快速上线的MVP项目
这是目前最主流的方案,90% 的新项目都这么干。
方案二:前后端合并部署(传统做法,逐渐减少)
把Vue打包后的dist文件放到SpringBoot的 resources/static 目录下,由SpringBoot统一提供页面和 API。
目录结构:
css
复制代码
src/
└── main/
├── java/ ← SpringBoot 代码
└── resources/
├── static/ ← Vue 打包后的 css/js
└── templates/ ← index.html(可选)
访问方式:
页面:http://localhost:8080/
API:http://localhost:8080/api/xxx
优点:
部署简单,一个 jar 包搞定
没有跨域问题
适合小型项目、内部系统
缺点:
前后端耦合,不利于独立迭代
静态资源由 Java 服务提供,性能不如 Nginx
不适合高并发场景
📌 适用场景:
内部工具、小项目
学习 demo
对性能要求不高的系统
这种方案在企业级项目中逐渐被淘汰,但在教学和小项目中依然常见。
方案三:双 Token 机制(高安全要求项目)
这是金融、银行、高权限系统中越来越流行的"专业做法"。
方案核心:
access_token:短期JWT,存前端内存,用于API认证
refresh_token:长期token,存HttpOnly Cookie,用于刷新access_token
流程:
登录成功
后端:Set-Cookie: refresh_token=xxx; HttpOnly; Secure
响应体:{ access_token: "yyy" }
前端:
存access_token到内存
请求时加 Authorization: Bearer yyy
access_token过期后:
调/refresh 接口
浏览器自动带refresh_token Cookie
拿到新access_token
优点:
XSS 攻不破(refresh_tokenJS 拿不到)
即使access_token泄露,有效期短(5-15分钟)
安全性极高
缺点:
实现复杂
需要后端配合
刷新机制要处理好并发
📌 适用场景:
银行、支付、高权限后台
对安全要求极高的系统
方案四:使用 Cookie + Session(传统安全做法)
SpringBoot使用Spring Security + Session,登录后Set-Cookie: JSESSIONID=xxx; HttpOnly
Vue 前端不需要管 token,浏览器自动带 Cookie。
优点:
安全性高(防 XSS)
后端可管理 session(如强制下线)
适合内网系统
缺点:
需要处理 CSRF
不适合无状态、微服务架构
跨域配置复杂
📌 适用场景:
传统企业系统
内网管理系统
已有 Spring Security 架构的项目
总结:四种方案对比
方案
安全性
易用性
推荐度
适用场景
前后端分离 + JWT + LocalStorage
⭐⭐⭐
⭐⭐⭐⭐⭐
⭐⭐⭐⭐☆
90% 的新项目
前后端合并部署
⭐⭐⭐
⭐⭐⭐⭐
⭐⭐⭐
小项目、学习
双 Token 机制
⭐⭐⭐⭐⭐
⭐⭐⭐
⭐⭐⭐⭐
高安全系统
Cookie + Session
⭐⭐⭐⭐
⭐⭐⭐
⭐⭐⭐⭐
传统企业系统
最终建议
如果你是新手或者做小项目:用前后端分离 + JWT + localStorage,简单直接。
如果你做中后台或者ToC产品:同上,但必须做好 XSS 防护(输入过滤、CSP、DOMPurify)。
如果你做金融或者高权限系统:上双 Token 机制,安全第一。
如果你是传统企业或者内网系统:可以考虑Cookie + Session,但要配好 CSRF。
目前 Vue + SpringBoot 的标准就是:前后端分离 + JWT + LocalStorage。
虽然它有 XSS 风险,但凭借开发效率高、架构清晰、适合云原生等优势,已经成为主流。
安全问题不是靠"不用 localStorage"解决的,而是靠:
严格的输入验证
CSP 策略
定期安全审计
使用Content-Security-Policy头
技术选型,永远是安全、效率、成本的权衡。
公众号:程序员刘大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!
📌往期精彩
《工作 5 年没碰过分布式锁,是我太菜还是公司太稳?网友:太真实了!》
《别再被 Stream.toMap() 劝退了!3 个真实避坑案例,建议收藏》
《写给小公司前端的 UI 规范:别让页面丑得自己都看不下去》
《终于找到 Axios 最优雅的封装方式了,再也不用写重复代码了》