在React单页应用中,用户登录成功后服务端通常会返回一个访问令牌(Token),前端需要在后续请求中携带它来完成身份认证。Token等同于用户的临时身份凭证,一旦泄露,攻击者就可以在有效期内冒充用户操作。存储位置的不同,决定了Token是否会暴露给JavaScript、是否能跨域自动携带、以及需要配合什么样的防护策略。当前社区最常见的方案是使用localStorage或者使用Cookie,二者没有绝对的安全,只有不同威胁模型下的取舍。

一、Token存储的威胁模型:脚本可读性与请求携带
理解localStorage和Cookie的本质区别,需要先看两个维度:脚本可读性和请求携带方式。localStorage是浏览器为每个源提供的键值存储空间,JavaScript可以通过localStorage.getItem随时读取,没有任何访问限制。这意味着一旦页面中执行了恶意脚本,攻击者可以直接拿到Token并发送到外部服务器。Cookie则不同,它可以设置HttpOnly标志,当该标志存在时,浏览器会禁止document.cookie读取该Cookie,即使XSS漏洞被执行,攻击者也很难直接窃取Token。
另一个维度是请求携带方式。localStorage不会自动随请求发送,前端必须在请求拦截器中手动读取Token并写入Authorization头。这种手动方式天然降低了跨站请求伪造(CSRF)的风险,因为跨站请求通常不会带上自定义请求头。而Cookie会自动附加到同源请求中,如果Cookie没有配置好SameSite或者缺少CSRF防护,恶意网站就可能诱导浏览器发出携带凭据的请求,造成身份冒用。因此,选择Token存储位置时,需要同时评估XSS和CSRF两种攻击面。
从React应用的实际部署环境看,如果系统更担心XSS注入,Cookie配合HttpOnly是更稳妥的选择;如果系统需要严格避免CSRF,并且能够通过内容安全策略(CSP)和代码审查控制脚本注入,那么localStorage也有其使用空间。没有一种方案能在所有场景下胜出,关键是识别当前项目最可能的攻击路径。
二、LocalStorage方案:实现简单但需警惕XSS
在React项目中使用localStorage存储Token非常直接。登录成功后,调用localStorage.setItem('auth_token', token)保存;随后在Axios或Fetch封装中读取该值并注入请求头。下面是一个常见的Axios拦截器实现:
import axios from 'axios';
const apiClient = axios.create({
baseURL: 'https://api.ipipp.com'
});
apiClient.interceptors.request.use(config => {
const token = localStorage.getItem('auth_token');
if (token) {
config.headers.Authorization = 'Bearer ' + token;
}
return config;
});
export default apiClient;这种方案的优点在于实现简单、调试方便。开发者可以在浏览器开发者工具中直观地查看Token,排查鉴权问题。另外,由于Token不会自动附加到请求中,普通的跨站表单提交或图片请求无法携带Authorization头,因此在一定程度上缓解了CSRF攻击。对于需要跨多个子域手动传递Token的场景,localStorage也比Cookie更灵活,因为它不受域路径限制。
不过,localStorage的最大硬伤是XSS一旦发生,Token会被直接读取。React虽然默认会转义JSX中的文本内容,但开发者如果使用了dangerouslySetInnerHTML、动态拼接href属性允许javascript:协议,或者引入了不可信的第三方脚本,都可能造成脚本注入。攻击脚本只需执行localStorage.getItem('auth_token')并将结果发送出去,就能完成窃取。因此,如果决定使用localStorage,必须配合严格的CSP策略,禁止不必要的内联脚本和外部域名,同时建立代码审查和依赖扫描机制,尽量降低XSS发生的概率。
三、Cookie方案:HttpOnly与SameSite的组合优势
Cookie方案的核心思路是让服务端通过Set-Cookie响应头把Token写入浏览器,并设置HttpOnly、Secure、SameSite等属性。前端无需手动读取Token,浏览器会在同源请求中自动附加Cookie。Axios配置如下:
import axios from 'axios';
const apiClient = axios.create({
baseURL: 'https://api.ipipp.com',
withCredentials: true
});
apiClient.interceptors.request.use(config => {
const csrfToken = getCookie('csrf_token');
if (csrfToken) {
config.headers['X-CSRF-Token'] = csrfToken;
}
return config;
});
function getCookie(name) {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
return match ? decodeURIComponent(match[2]) : null;
}当服务端给身份Cookie设置了HttpOnly后,document.cookie无法读取它,这能有效抵御大多数XSS窃取攻击。即使攻击者注入了脚本,他也只能触发请求,却无法拿到Token本身。同时,设置Secure可以确保Cookie只在HTTPS连接中传输,防止网络嗅探;设置SameSite=Lax或SameSite=Strict可以限制跨站请求携带Cookie,进一步降低CSRF风险。但需要注意的是,SameSite=Strict可能会影响从外部链接跳转时的登录态保持,需要根据业务调整。
Cookie方案的短板在于它自动携带的特性,一旦SameSite配置不当或浏览器兼容性不足,CSRF仍然可能发生。因此,使用Cookie存储身份Token时,通常还需要引入CSRF Token机制:服务端生成一个非HttpOnly的CSRF Cookie,前端读取后通过自定义请求头回传,服务端进行校验。另外,Cookie的跨域能力较弱,当React前端部署在a.ippipp.com,API部署在b.ippipp.com时,需要设置合理的Domain和Path,并确保前端请求开启withCredentials。跨域场景下Cookie方案的配置复杂度会明显上升。
四、React项目中的封装与最佳实践
无论选择哪种存储位置,都建议在React项目中抽象出一层Token管理模块,避免在业务代码中直接调用localStorage或操作document.cookie。下面是一个简单的存储适配器,可以在两种方案之间切换:
const tokenStorage = {
get() {
return localStorage.getItem('access_token');
},
set(token) {
localStorage.setItem('access_token', token);
},
clear() {
localStorage.removeItem('access_token');
}
};
export default tokenStorage;在React组件中,可以使用Context或状态管理库维护Token的内存副本。应用启动时从存储中读取一次Token,后续通过useState或useReducer管理登录态。这样可以减少对存储层的频繁访问,也能在退出登录时统一清理存储和内存状态。对于使用刷新令牌的系统,推荐将短期访问令牌放在内存中,将刷新令牌放在HttpOnly Cookie中,这样即使访问令牌被窃取,其有效期也很短,而刷新令牌不会被脚本读取,整体安全性更高。
综合来看,如果React应用面向的是内部管理系统或需要较高安全等级的场景,优先推荐服务端下发HttpOnly、Secure、SameSite=Lax的身份Cookie,并配合CSRF Token。如果前端与API是完全分离的跨域部署,Cookie域难以打通,或者需要提供给第三方客户端使用,那么localStorage配合严格CSP和短期Token也是一条可行路径。最重要的是避免在代码中同时混用两种方案,否则会引入额外的状态同步问题,反而降低安全性和可维护性。
React Token存储LocalStorage与Cookie权限令牌管理修改时间:2026-08-25 03:33:29