Token 存储与安全防护
说说看,用户登录后拿到的 Token,前端应该怎么存?
一、Token 存储方案
Token 是用户身份认证的核心凭证,存储方式直接影响安全性。
1. localStorage
最便捷的存储方式,登录成功后,前端通过 localStorage.setItem('token', token) 存储。
使用方式:请求时手动在 Authorization 头中携带。
优势:持久化存储,关闭浏览器后不丢失,操作简单。
缺点:易受 XSS 攻击(恶意脚本可通过 localStorage 窃取 Token),无法跨域访问(同域名下的不同页面可共享)。
2. Cookie
完整方案:Cookie(HttpOnly + Secure) + 防御 CSRF,后端必须部署完善的 CSRF 防护策略。
通过后端设置 Set-Cookie 头,将 Token 存入浏览器 Cookie,并配置以下属性:
HttpOnly: 设置了HttpOnly的 Cookie 无法通过 JavaScript 的document.cookieAPI 访问(防止 XSS 攻击窃取 Token)。Secure: 仅在 HTTPS 协议下传输(防止在网络传输中被窃听)。SameSite: 限制跨域请求携带 Cookie(防止 CSRF 攻击,建议设为SameSite=Strict或Lax)。Max-Age/Expires: 设置过期时间(避免永久有效)。
使用方式:前端无需手动存储,浏览器会自动在请求头的 Cookie 中携带 Token,前端只需正常调用接口即可。
优势:彻底避免 XSS 攻击(JS 无法读取),配合 SameSite 可防御 CSRF。
缺点:跨域请求需后端配合配置 CORS(如 Access-Control-Allow-Credentials: true)。前端无法主动修改 / 删除 Token(需通过后端接口处理)。
3. 双 Token
双 Token 方案通过 “短期业务令牌 + 长期刷新令牌” 的组合,既解决了 localStorage 的 XSS 风险和 Cookie 的 CSRF 风险,又保证了用户体验,是目前 API 认证的最优解之一。
登录阶段:
用户提交账号密码 → 后端验证通过后,返回两个令牌:
access_token:短期有效(例如 2 小时),用于请求受保护的 API。refresh_token:长期有效(例如 7 天),仅用于获取新的access_token,不应具备API访问权限。
存储策略:
access_token:存入内存(如 Vue/React 的状态管理库、JavaScript 变量)。这样即使被 XSS 窃取,有效期也很短,风险可控。refresh_token:存入 HttpOnly Cookie。因为它有效期长,必须严加保护。由于其本身不直接用于业务请求,即使遭遇 CSRF,攻击者也无法用它来做任何关键操作(只能用来换一个短期的access_token,而该access_token又会因为存在于内存而难以被攻击者获取)。
例:
Set-Cookie: refresh_token=xxx; HttpOnly; Secure; SameSite=Strict; Max-Age=604800(7 天有效期)。
无感刷新:
当 access_token 过期后,前端自动处理,用户无感知。
- 前端通过请求拦截器捕获到 401 响应后,自动发起 “刷新令牌” 的请求(如调用
/api/refresh-token接口),浏览器会自动将refresh_token从 Cookie 中取出并携带在请求头中。 - 后端验证并返回新的 
access_token,前端更新并使用新的access_token。 
登出阶段:
- 前端调用登出接口,后端清除服务器端的
refresh_token记录,并通过Set-Cookie清除客户端 Cookie 中的refresh_token。 - 前端清除内存中的
access_token。 
二 、JWT
JWT(JSON Web Token)是一种基于 JSON 的轻量级身份认证令牌,用于在客户端和服务器之间安全传递信息。它通过数字签名确保信息的完整性和真实性,常被用于前后端分离、分布式系统中的身份验证和信息交换。
1. JWT 的核心作用
身份认证:用户登录后,服务器生成 JWT 返回给客户端,客户端后续请求携带 JWT,服务器通过验证 JWT 确认用户身份(无需频繁查询数据库校验)。
信息传递:可在 JWT 中嵌入非敏感的用户信息(如用户 ID、角色),减少服务器查询数据库的次数。
2. JWT 组成
JWT 由三部分构成,每部分都是 Base64 编码的字符串(非加密,但可被解码,因此不能存敏感信息),整体格式为:header.payload.signature
Ⅰ. Header(头部)
作用:声明令牌类型(typ: "JWT")和签名算法(如 HS256、RS256)。
处理:Base64 编码后成为 JWT 的第一部分。
Ⅱ. Payload(载荷)
作用:存储非敏感的用户信息(如用户 ID、角色、过期时间),包含标准字段和自定义字段。
Ⅲ. Signature(签名)
作用:防止令牌被篡改,是 JWT 安全性的核心。
生成方式:服务器使用 Header 中声明的算法(如 HS256),结合服务器端密钥(绝对不能泄露给客户端),对前两部分(Header+Payload)进行签名。
验证逻辑:客户端携带 JWT 请求时,服务器会重新计算签名,若与 JWT 中的 signature 一致,则令牌未被篡改且有效。
3. JWT 与 Token 区别
① Token(令牌):是一个抽象概念,指 “用于身份验证或授权的字符串凭证”。任何能证明身份的字符串都可以称为 Token,例如:
Token 的核心作用是:客户端用它来证明 “我是谁”,服务器用它来验证身份。
- 随机生成的一串无意义字符(如 
a1b2c3d4...,传统 Session 对应的 SessionID 本质也是一种 Token)。 - 包含结构化信息的字符串(如 JWT)。
 
② JWT(JSON Web Token):是一种 标准化的 Token 格式,它规定了 Token 必须包含三部分(Header + Payload + Signature),且通过 JSON 结构存储信息、通过数字签名保证安全性。
简单说:JWT 是 Token 的 “一种特定格式”,它比普通随机字符串 Token 多了 “结构化信息” 和 “签名验证” 的特性。
总结:Token 是统称,所有用于身份验证的凭证都叫 Token。JWT 是 Token 的一种,是一种包含 JSON 信息、带签名的标准化 Token,解决了普通 Token 无法携带信息、难以验证完整性的问题。
三、XSS
1. 什么是 XSS?
XSS(Cross-Site Scripting,跨站脚本攻击)是攻击者在网页中注入恶意脚本(通常是 JavaScript),当用户访问该页面时,恶意脚本会在用户浏览器中执行,从而窃取用户信息(如 Cookie、Token)、篡改页面内容或发起恶意操作。
示例场景:
- 一个论坛允许用户发布内容,攻击者发布包含
<script>alert(document.cookie)</script>的评论。 - 其他用户查看该评论时,恶意脚本会执行,弹出当前用户的 Cookie(若包含登录凭证,攻击者可盗用身份)。
 
2. 为什么localStorage易受 XSS 攻击?
localStorage是前端 JavaScript 可直接访问的存储对象(通过localStorage.getItem/setItem操作)。
- 若页面存在 XSS 漏洞,攻击者注入的恶意脚本可直接读取
localStorage中的数据(如 Token、用户信息)。 - 例如:
script标签中的恶意代码可通过localStorage.getItem('token')窃取存储的 Token,进而伪造用户身份。 
3. 为什么 HttpOnly Cookie 可防止 XSS 攻击?
Cookie 的HttpOnly属性是后端设置的安全标识,作用是:禁止前端 JavaScript 通过document.cookie访问该 Cookie。
4. 转义
转义可以解决 “恶意脚本注入执行” 的问题,这是 XSS 攻击的核心环节 —— 如果没有转义,攻击者可以直接注入脚本并执行,进而窃取localStorage、Cookie(非 HttpOnly)等数据。
论坛如果直接将用户输入的内容 “原样输出” 到页面中,那么包含的 HTML/JavaScript 代码会被浏览器解析为可执行代码,而不是普通字符串,按照 HTML 语法解析。
实现方法:将用户输入中的 HTML 特殊字符(如 <、>、&、" 等)转义为 “实体字符”,让浏览器将其当作普通文本显示,而非可执行代码。
转义只能防止 “用户输入的内容被解析为脚本执行”(如评论区的<script>标签),但无法阻止已经执行的恶意脚本访问 localStorage,例如假设攻击者通过其他漏洞(如第三方组件漏洞、未过滤的 URL 参数)在页面中注入了恶意脚本。
四、CSRF
1. 什么是 CSRF?
CSRF(Cross-Site Request Forgery,跨站请求伪造):攻击者诱导已登录的用户在不知情的情况下,向目标服务器发送恶意请求(利用用户的登录状态)。由于请求携带用户的 Cookie,服务器会误认为是用户主动操作,从而执行恶意行为(如转账、修改密码)。
示例场景:
- 用户登录了银行网站(
bank.com),浏览器保存了银行的登录 Cookie。 - 攻击者诱导用户访问恶意网站(
evil.com),该网站隐藏了一个指向银行转账接口的请求:<img src="https://bank.com/transfer?to=攻击者账号&amount=1000"> - 用户访问
evil.com时,浏览器会自动携带bank.com的 Cookie 发送请求,银行服务器验证 Cookie 有效后,执行转账操作。 
2. 如何避免 CSRF 攻击?
核心思路是:让服务器能区分请求是否由用户主动发起,常用方案如下:
| 防御手段 | 原理 | 为什么能避免 CSRF? | 
|---|---|---|
| 1. 验证码 / 二次确认 | 要求用户手动输入验证码或确认操作(如转账时输入密码)。 | 攻击者无法绕过用户的手动操作,恶意请求会因缺少验证码而失败。 | 
| 2. CSRF Token | 服务器生成随机 Token(如csrf_token),嵌入页面表单或请求头中。 | 恶意网站无法获取该 Token(跨域限制),服务器验证请求中是否包含有效 Token,缺失则拒绝。 | 
| 3. SameSite Cookie 属性 | 后端设置 Cookie 的SameSite属性(Strict或Lax)。 | 限制 Cookie 仅在同域请求中携带,跨域的恶意请求(如evil.com向bank.com发请求)不会携带 Cookie,服务器无法验证身份。 | 
| 4. 验证 Referer/Origin 头 | 服务器检查请求的Referer(来源页面)或Origin(来源域名)是否合法。 | 恶意请求的Referer是evil.com,服务器识别后拒绝处理。 | 
三、总结
| 攻击类型 | 核心危害 | 防御核心手段 | 
|---|---|---|
| XSS | 注入恶意脚本,窃取数据 | 1. 输入过滤 / 输出编码;2. 用HttpOnly保护 Cookie;3. 禁用localStorage存储敏感信息。 | 
| CSRF | 伪造用户请求,执行恶意操作 | 1. 使用 CSRF Token;2. 设置SameSite Cookie;3. 验证 Referer/Origin。 | 
