
你有没有过这样的经历?项目上线前突然发现 Token 存储出了问题 —— 要么刷新页面后用户直接登出,要么被测试测出 XSS 漏洞风险,紧急修改时还得兼顾兼容性,最后加班到半夜才搞定?
作为前端开发,我们每天和用户认证打交道,而 Token 存储看似是基础操作,却藏着不少容易踩的坑。今天就从实际开发中的问题出发,跟大家聊聊 Token 该怎么存、不同场景下该选哪种方案,帮你避开那些 “上线即翻车” 的坑。
上周帮同事排查一个线上 bug 时,发现他们的项目把 Token 存在了 sessionStorage 里,结果用户打开新标签页登录后,原标签页刷新就提示 “登录已过期”。后来查了才知道,sessionStorage 是会话级存储,每个标签页都是独立的会话,这就导致多标签页场景下 Token 无法共享。
还有一次更惊险的,之前参与的一个电商项目,为了图方便把 Token 存在了 localStorage 里,上线后没几天就被安全团队测出 XSS 漏洞 —— 攻击者通过注入脚本获取了 localStorage 里的 Token,模拟用户登录操作,差点造成用户信息泄露。
这些问题实则不是个例,我在开发者社区做了个小调研,发现超过 60% 的前端开发者都在 Token 存储上踩过坑,其中 “多标签页共享失效”“XSS 攻击风险”“Token 过期处理混乱” 是最常见的三类问题。而这些问题的根源,往往是我们一开始对 Token 存储方案的选型思考不够全面。
在聊解决方案之前,我们得先明确一个问题:Token 到底是用来干嘛的?简单说,Token 是服务器给客户端的 “身份凭证”,客户端每次发起请求时带上 Token,服务器通过验证 Token 来确认用户身份。所以 Token 的存储,本质上是要解决 “如何安全、稳定地保存这个凭证” 的问题。
目前前端常用的 Token 存储方案主要有三种:localStorage、sessionStorage、Cookie,这三种方案的底层特性完全不同,也决定了它们的适用场景:
这里要特别提醒大家,许多开发者会忽略 Token 的 “过期机制”——Token 一般会设置过期时间(列如 Access Token 有效期 2 小时,Refresh Token 有效期 7 天),如果存储方案没思考过期后的刷新逻辑,就会导致用户在操作过程中突然被踢下线,影响用户体验。
知道了底层逻辑和常见问题,接下来就针对不同场景,给出具体的解决方案,每个方案都附了实际项目中用过的代码示例,大家可以直接参考。
如果你的项目是纯 SPA,用户一般只在一个标签页操作(列如管理后台),而且对安全性要求不是特别高(列如内部系统,不是面向外部用户的电商、金融类应用),可以思考用sessionStorage + Token 过期刷新的方案。
具体实现步骤:
代码示例(基于 Axios 拦截器):
// 登录成功后存储Token
const handleLogin = async (username, password) => {
const res = await axios.post('/api/login', { username, password });
const { accessToken, refreshToken } = res.data;
// Access Token存入sessionStorage
sessionStorage.setItem('accessToken', accessToken);
// Refresh Token存入Cookie,设置HttpOnly、SameSite
document.cookie = `refreshToken=${refreshToken}; HttpOnly; SameSite=Strict; Path=/`;
};
// Axios请求拦截器:添加Token
axios.interceptors.request.use(config => {
const accessToken = sessionStorage.getItem('accessToken');
if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`;
}
return config;
});
// Axios响应拦截器:处理Token过期
axios.interceptors.response.use(
response => response,
async error => {
const originalRequest = error.config;
// 避免重复刷新Token
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
// 调用刷新Token接口
const res = await axios.post('/api/refreshToken');
const { accessToken } = res.data;
// 更新sessionStorage中的Token
sessionStorage.setItem('accessToken', accessToken);
// 重新发起原请求
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
return axios(originalRequest);
} catch (err) {
// 刷新Token失败,跳转到登录页
window.location.href = '/login';
return Promise.reject(err);
}
}
return Promise.reject(error);
}
);如果你的项目是多标签页应用(列如电商网站、社交平台),用户可能会同时打开多个标签页操作,这时候 sessionStorage 的 “不共享” 特性就会出问题,推荐用Cookie + HttpOnly + SameSite的方案。
具体实现步骤:
代码示例(后端设置 Cookie,以 Node.js Express 为例):
// 登录接口:设置Token到Cookie
app.post('/api/login', (req, res) => {
const { username, password } = req.body;
// 验证用户身份(省略逻辑)
const accessToken = generateAccessToken(username); // 生成Access Token
// 设置Cookie
res.cookie('accessToken', accessToken, {
httpOnly: true, // 禁止JS读取
sameSite: 'strict', // 防止CSRF
secure: process.env.NODE_ENV === 'production', // 生产环境开启HTTPS
maxAge: 7200 * 1000, // 2小时过期(毫秒)
path: '/' // 所有路径都可访问
});
res.json({ success: true, message: '登录成功' });
});
// 验证Token的中间件
const verifyToken = (req, res, next) => {
const accessToken = req.cookies.accessToken;
if (!accessToken) {
return res.status(401).json({ message: '请先登录' });
}
// 验证Token(省略逻辑)
next();
};
// 需要登录的接口
app.get('/api/userInfo', verifyToken, (req, res) => {
// 返回用户信息
res.json({ username: req.username, role: req.role });
});如果你的项目涉及用户资金、敏感信息(列如支付、理财类应用),对安全性要求极高,推荐用Cookie + HttpOnly + SameSite + 双重 Token 验证的方案。
具体实现逻辑:
这种方案的优势在于,即使 Access Token 被窃取,由于有效期很短,攻击者能利用的时间窗口超级小;而 Refresh Token 虽然有效期长,但由于存在服务器黑名单,登出后就能立即失效,安全性大大提升。
看到这里,可能有同学会说 “场景太多,记不住怎么办?” 实则不用死记硬背,只要按照这 3 步来,就能快速选出适合自己项目的 Token 存储方案:
第一步:明确项目场景 —— 是单标签页还是多标签页?是否涉及敏感信息?
第二步:优先思考安全性 —— 如果是高安全性需求,直接选 Cookie + HttpOnly;如果是普通内部系统,sessionStorage 也能用,但要注意 XSS 防护。
第三步:处理边界问题 —— 多标签页要思考 Token 共享,长期登录要思考 Token 过期刷新,这些细节必定要在前期就规划好,避免上线后返工。
最后,想跟大家说的是,Token 存储虽然是个小知识点,但却直接关系到项目的安全性和用户体验。如果你在实际开发中遇到了 Token 相关的问题,欢迎在评论区留言,列如 “你项目中用的是哪种存储方案?踩过哪些坑?”,我们一起交流探讨,相互避坑~