前端登录token到底应该存在哪?LocalStorage、SessionStorage还是Cookie?一篇说透!
大家好,我是大华!
前几天有个小伙伴问我:“我登录之后拿到了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最方便啊!”
两行代码搞定:
// 存
localStorage.setItem('token', res.token);
// 发请求时
axios.defaults.headers.common['Authorization'] = localStorage.getItem('token');
方便、简单是真的,但同时也会伴随着安全隐患。
案例1:XSS攻击,Token被偷
假设你网站有个评论区,用户输入没做转义:
<script>
fetch('/steal?token=' + localStorage.getItem('token'))
</script>
用户打开页面,这条JS一执行,你的Token就会被发送到黑客服务器上。
而如果 Token 在HttpOnly Cookie
里,JS 读不到,XSS 攻击直接失效。
三、用Cookie就完美了吗?
不,Cookie
也有坑。黑客诱导你访问一个恶意页面:
<img src="https://yourbank.com/transfer?to=hacker&amount=100000" />
如果你的登录态在 Cookie 里,浏览器会自动带上 Cookie,请求就成功了!
用户没点确认,钱就没了。
解决方案
方案一:前后端分离 + JWT + LocalStorage(最常见)
这是目前绝大多数新项目采用的方式。
技术栈:
- 前端:Vue3 + Vue CLI / Vite,部署在 Nginx / CDN
- 后端:SpringBoot,提供 RESTful API
- 通信:Axios + JWT(JSON Web Token)
-
Token存储:
localStorage
或内存
部署方式:
用户浏览器
↓
Vue 前端(http://fe.yourcompany.com) ←→ SpringBoot 后端(http://api.yourcompany.com)
前端和后端完全独立部署,通过CORS跨域通信。
认证流程:
- 用户登录
- SpringBoot验证用户名密码,生成JWT
- 返回给前端:
{ token: "xxxxxx" }
- 前端存入
localStorage
- 后续请求,前端手动加Header:
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。
目录结构:
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_token
JS 拿不到) - 即使
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 个真实避坑案例,建议收藏》