Token 存 localStorage 安全吗?为什么登录后还能被 CSRF 攻击?
我最近在做登录功能,后端返回的 token 我直接存到了 localStorage 里,每次请求手动加到 Authorization 头。但听说这样容易被 XSS 拿走,而且好像还是防不住 CSRF?有点懵。
比如我现在这个登录后的页面,会自动发个请求获取用户信息:
fetch('/api/user', {
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('token')
}
})
但同事说如果网站有 XSS 漏洞,攻击者就能读取 localStorage;而如果用 Cookie 存 token 又可能被 CSRF 利用。到底该怎么存才安全?有没有兼顾的办法?
但你说防不住 CSRF,这事儿得分情况看。你现在用的是 Authorization 头手动加 token,这种方式其实不太容易被 CSRF 攻击,因为恶意页面没办法自动设置 Authorization 头——它又读不到你的 localStorage。CSRF 能成功利用的是浏览器会自动带上 Cookie 这个特性,跟 Authorization 头没关系。
你同事说的"Cookie 存 token 会被 CSRF 利用",指的是把 token 存到 Cookie 里,然后靠 Cookie 自动发送来完成认证这种情况。这种情况下确实存在 CSRF 风险,因为浏览器会自动把 Cookie 带到跨域请求里。
那怎么兼顾?业界常见的做法是:
token 存 Cookie 里,但要设置 SameSite 属性。SameSite=Strict 最安全,完全禁止跨域带 Cookie;SameSite=Lax 允许部分 GET 请求带 Cookie,但 POST 这种会限制。如果用 SameSite=Strict,基本就能挡住大多数 CSRF 攻击了。
如果觉得 SameSite=Lax 不够用,还可以再搭配一个 CSRF Token。后端生成一个随机 token,存到 Cookie 里(不设置 SameSite,或者设置为 Lax),然后前端在请求时从 Cookie 读取这个值,放到自定义头或者请求体里。后端同时验证 token 和 Referer,这样就更稳妥。
还有个常见的 Double Submit 模式:CSRF Token 既存在 Cookie 里,也存在请求参数或头里,后端比对两者是否一致。这种不需要服务端存储,适合分布式场景。
简单总结一下:localStorage + Authorization 头这种方式,XSS 风险大,但 CSRF 风险相对小;Cookie + SameSite + CSRF Token 这种组合能兼顾两边。你现在如果想改,可以考虑把 token 存 Cookie,设置 SameSite=Strict,再配个 CSRF Token,这样基本就稳了。