Token存储选择:LocalStorage、Cookie还是内存?几种方案对比
很多开发者刚开始做项目时都会遇到这个问题:用户登录后拿到的token,到底应该存在哪里?LocalStorage、SessionStorage、Cookie,还是直接放在内存中?为什么不同的网站做法不一样?
这个问题确实让人困惑。每次看到不同项目使用不同的存储方式,很多人都会想:到底哪种方法才是正确的?
为什么token存储这么重要?
想象一下,你家的钥匙你会放在哪里?随身带着?藏在门垫下面?还是交给保安保管?如果放错了地方,小偷就可能进你家门。
token就是用户进入系统的钥匙。如果存错了地方,黑客就能冒充用户登录账号,后果很严重。
不同存储方式的特点
1. LocalStorage(本地存储)
永久保存,除非手动删除
同一个网站的任何页面都能读取
刷新页面或关闭浏览器都不会丢失
如果网站有安全漏洞,token可能被偷
需要手动添加到请求头中
2. SessionStorage(会话存储)
只在当前浏览器标签页有效
同一个网站可以读取
适合临时操作
关闭浏览器标签页就消失
同样有安全风险
需要手动管理
3. Cookie
可以设置过期时间
可以设置HttpOnly(JavaScript读不到)
可以设置Secure(只通过HTTPS传输)
可以设置SameSite(防止某些攻击)
自动随着请求发送
容量有限制(约4KB)
每次请求都会携带,可能浪费流量
4. 内存存储
页面刷新就丢失
完全由前端控制,不持久保存
最快最安全,但生命周期最短
不适合需要持久保存的情况
关闭标签页就没了
为什么很多人喜欢用LocalStorage?
很多前端开发者的第一反应是:"用LocalStorage最方便!"
确实,几行代码就能搞定:
// 保存token
localStorage.setItem('token', res.token);
// 发送请求时使用
axios.defaults.headers.common['Authorization'] = localStorage.getItem('token');方便是真的方便,但安全问题也需要考虑。
安全例子: 如果网站有个评论区,用户输入的内容没有做安全检查,黑客可能插入这样的代码:
<script>
fetch('/steal?token=' + localStorage.getItem('token'))
</script>用户打开页面时,这段代码执行,你的token就被发送到黑客的服务器了。
如果token存在HttpOnly Cookie里,JavaScript读不到,这种攻击就失效了。
用Cookie就绝对安全吗?
也不是。Cookie也有自己的问题。比如黑客可能诱导你访问一个恶意页面:
<img src="https://yourbank.com/transfer?to=hacker&amount=100000" />如果你的登录信息在Cookie里,浏览器会自动带上Cookie,请求就可能成功。用户什么都没做,钱可能就被转走了。
四种实用的解决方案
方案一:前后端分离 + JWT + LocalStorage(最常用)
这是目前大多数新项目采用的方式。
技术组合:
部署方式:
用户浏览器访问前端网站,前端通过API与后端通信。
登录流程:
用户登录
后端验证用户名密码,生成JWT
返回给前端:{ token: "xxxxxx" }
前端存入localStorage
后续请求时,前端手动添加请求头:
axios.defaults.headers.common['Authorization'] = 'Bearer ' + token后端解析JWT,验证身份
优点:
前后端完全分开,各自独立开发部署
适合现代架构
开发简单,调试方便
缺点:
有安全风险
需要手动管理token
跨域配置稍麻烦
适合场景:
后台管理系统
普通网站、商城
需要快速上线的项目
这是目前最主流的方案,大部分新项目都这样用。
方案二:前后端合并部署(传统做法)
把前端代码打包后放到后端的静态资源目录里,由后端统一提供页面和API。
访问方式:
页面:http://localhost:8080/
API:http://localhost:8080/api/xxx
优点:
部署简单
没有跨域问题
适合小型项目
缺点:
前后端耦合,不利于独立更新
静态资源由后端服务提供,性能可能不如专用服务器
不适合高并发场景
适合场景:
内部工具
学习项目
对性能要求不高的系统
这种方案在企业级项目中逐渐减少,但在小项目中仍然常见。
方案三:双Token机制(高安全要求)
这是银行、金融等高安全性系统中常用的专业做法。
方案核心:
access_token:短期JWT,存在前端内存,用于API认证
refresh_token:长期token,存在HttpOnly Cookie,用于刷新access_token
流程:
登录成功
后端:设置HttpOnly Cookie存储refresh_token
响应体:返回access_token
前端:
存access_token到内存
请求时添加Authorization头
access_token过期后:
调用刷新接口
浏览器自动携带refresh_token
获取新的access_token
优点:
安全性高
即使access_token泄露,有效期也很短
极高的安全性
缺点:
实现复杂
需要后端配合
要处理好刷新机制
适合场景:
银行、支付系统
高权限后台
对安全要求极高的系统
方案四:使用Cookie + Session(传统安全做法)
使用Spring Security + Session,登录后设置HttpOnly的Session Cookie。
前端不需要处理token,浏览器会自动携带Cookie。
优点:
安全性高
后端可以管理session
适合内部系统
缺点:
需要处理跨站请求伪造问题
不适合现代架构
跨域配置复杂
适合场景:
传统企业系统
内部管理系统
已有相关架构的项目
四种方案对比
| 方案 | 安全性 | 易用性 | 推荐度 | 适用场景 |
|---|---|---|---|---|
| 前后端分离 + JWT + LocalStorage | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | 90%的新项目 |
| 前后端合并部署 | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | 小项目、学习 |
| 双Token机制 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | 高安全系统 |
| Cookie + Session | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | 传统企业系统 |
实用建议
如果你是新手或做小项目:用前后端分离 + JWT + localStorage,简单直接
如果你做后台系统或普通网站:同上,但要做好安全防护(输入检查、安全策略等)
如果你做金融或高权限系统:用双Token机制,安全第一
如果你是传统企业或内部系统:可以考虑Cookie + Session,但要配置好安全设置
目前Vue + SpringBoot的标准做法是:前后端分离 + JWT + LocalStorage。
虽然这种方案有安全风险,但因为开发效率高、架构清晰、适合现代部署等优势,已经成为主流。
安全问题不是单靠"不用localStorage"就能解决的,而是需要:
严格的输入验证
安全策略配置
定期安全检查
使用安全头部
技术选择,永远是在安全、效率、成本之间找到平衡。
实际代码示例
使用LocalStorage的基本例子
// 登录后保存token
function loginSuccess(token) {
localStorage.setItem('auth_token', token);
}
// 发送请求时添加token
function apiRequest(url, data) {
const token = localStorage.getItem('auth_token');
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(data)
});
}
// 退出登录时清除
function logout() {
localStorage.removeItem('auth_token');
}双Token机制示例
let accessToken = '';
// 登录处理
async function login(credentials) {
const response = await fetch('/api/login', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(credentials)
});
const data = await response.json();
accessToken = data.access_token;
// refresh_token会自动保存在HttpOnly Cookie中
}
// 自动刷新token
async function refreshToken() {
try {
const response = await fetch('/api/refresh', {
method: 'POST',
credentials: 'include' // 自动携带Cookie
});
const data = await response.json();
accessToken = data.access_token;
return true;
} catch (error) {
return false;
}
}选择哪种方案,要根据你的具体需求和项目特点来决定。对于大多数项目来说,方案一就足够了,但要知道它的优缺点,并做好相应的安全措施。
本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!