☰
axios自动携带CSRF Token的实现与安全实践
2026/10/28 17:41:38 网站建设 项目流程

1. 为什么我们需要自动携带CSRF Token

在Web应用开发中,CSRF(Cross-Site Request Forgery)攻击是一种常见的安全威胁。攻击者利用用户已登录的身份,在用户不知情的情况下执行非预期的操作。想象一下这样的场景:你登录了银行网站后,不小心点击了恶意链接,结果你的账户被自动转账——这就是典型的CSRF攻击。

CSRF Token的防御机制要求每个状态变更请求(POST、PUT、DELETE等)必须携带一个服务器颁发的随机Token。这个Token通常存储在:

  • 表单的隐藏字段中
  • 或者通过Cookie传递

但手动在每个请求中添加Token既繁琐又容易遗漏。这就是为什么我们需要在axios中实现自动携带CSRF Token的功能——它能让我们的应用更安全,同时减少开发者的重复劳动。

2. 理解CSRF Token的工作机制

2.1 服务器端的Token生成与验证

服务器通常会在用户会话开始时生成一个唯一的CSRF Token,并通过以下方式之一提供给客户端:

  1. 在渲染页面时嵌入到表单的隐藏字段中
  2. 通过Set-Cookie头设置到客户端的Cookie中

对于常见的实现方式,以Spring Security为例:

// Spring Security的默认CSRF保护配置 http.csrf(csrf -> csrf .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) );

这种配置会让服务器:

  1. 生成名为XSRF-TOKEN的Cookie
  2. 期望客户端在后续请求的X-XSRF-TOKEN头中带回这个值

2.2 客户端的Token存储与携带

客户端需要完成以下工作流程:

  1. 从Cookie或DOM中提取CSRF Token
  2. 在后续的修改请求中自动附加这个Token
  3. 确保Token在页面刷新时能正确更新

3. 实现axios请求拦截器自动携带Token

3.1 基础拦截器实现

以下是完整的axios拦截器实现代码:

import axios from 'axios'; // 创建axios实例 const http = axios.create({ baseURL: process.env.VUE_APP_API_BASE_URL, withCredentials: true // 允许携带cookie }); // 请求拦截器 http.interceptors.request.use(config => { // 只对写操作添加CSRF Token if (['post', 'put', 'delete', 'patch'].includes(config.method.toLowerCase())) { // 从cookie中获取CSRF Token const csrfToken = getCookie('XSRF-TOKEN'); if (csrfToken) { // 添加到请求头 config.headers['X-XSRF-TOKEN'] = csrfToken; } } return config; }, error => { return Promise.reject(error); }); // 从cookie中获取指定值的辅助函数 function getCookie(name) { const value = `; ${document.cookie}`; const parts = value.split(`; ${name}=`); if (parts.length === 2) return parts.pop().split(';').shift(); } export default http;

3.2 关键实现细节解析

  1. withCredentials配置:

    • 必须设置为true才能让浏览器在跨域请求中携带Cookie
    • 对应服务器Access-Control-Allow-Credentials头也要设为true
  2. 请求方法过滤:

    • 我们只对可能修改数据的请求方法添加CSRF Token
    • GET、HEAD等安全方法通常不需要保护
  3. Cookie处理:

    • 直接从document.cookie中解析需要的Token值
    • 注意处理Cookie不存在的情况

4. 高级配置与优化

4.1 双Token模式增强安全性

更安全的实现可以采用双Token模式:

  1. HttpOnly Cookie:存储主Token,JavaScript无法访问
  2. 可读Cookie:存储供JavaScript读取的副Token

服务器端配置示例(Spring Security):

http.csrf(csrf -> csrf .csrfTokenRepository(new CookieCsrfTokenRepository() { @Override public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) { // 主Token - HttpOnly String cookieValue = token == null ? "" : token.getToken(); Cookie cookie = new Cookie("CSRF-TOKEN", cookieValue); cookie.setHttpOnly(true); // 副Token - 可读 Cookie readableCookie = new Cookie("XSRF-TOKEN", cookieValue); response.addCookie(cookie); response.addCookie(readableCookie); } }) );

客户端相应调整:

// 修改getCookie调用为读取XSRF-TOKEN而非CSRF-TOKEN const csrfToken = getCookie('XSRF-TOKEN');

4.2 Token刷新机制

为防止Token过期,可以实现自动刷新机制:

http.interceptors.response.use(response => { return response; }, async error => { if (error.response.status === 403 && error.response.data.message === 'Invalid CSRF Token') { // Token过期,获取新Token后重试请求 await refreshCsrfToken(); return http(error.config); } return Promise.reject(error); }); async function refreshCsrfToken() { // 专门请求获取新Token await http.get('/auth/csrf'); // 服务器会在响应中设置新Cookie }

5. 常见问题与解决方案

5.1 跨域问题排查清单

当CSRF Token未正确携带时,按以下步骤排查:

  1. 检查浏览器开发者工具的Application > Cookies:

    • 确认XSRF-TOKEN Cookie存在且值正确
    • 确认Cookie的Domain和Path匹配你的API地址
  2. 检查请求的Request Headers:

    • 确认X-XSRF-TOKEN头存在且值正确
    • 确认withCredentials: true已设置
  3. 检查服务器CORS配置:

    @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); // 必须 config.addAllowedOrigin("https://yourdomain.com"); // 不能是* config.addAllowedHeader("*"); config.addAllowedMethod("*"); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); }

5.2 测试环境特殊处理

在测试环境中,你可能需要:

  1. 禁用CSRF保护进行测试:

    @Profile("test") @Configuration public class NoCsrfSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable(); } }
  2. 或者在测试代码中手动添加Token:

    // Jest测试示例 describe('API测试', () => { beforeAll(() => { // 设置测试用的CSRF Token document.cookie = 'XSRF-TOKEN=test-token'; }); it('应该自动携带CSRF Token', async () => { const res = await http.post('/api/data', {}); expect(res.status).toBe(200); }); });

6. 安全增强建议

6.1 同源策略检查

可以在拦截器中增加额外的安全层:

http.interceptors.request.use(config => { // 检查请求目标是否同源 const requestUrl = new URL(config.url, config.baseURL); const currentUrl = new URL(window.location.href); if (requestUrl.origin !== currentUrl.origin) { console.warn(`跨源请求到 ${requestUrl.origin},请确认是否合法`); // 可以在这里添加额外的安全验证 } // ...原有CSRF逻辑 return config; });

6.2 敏感操作二次验证

对于关键操作(如转账、密码修改),建议:

  1. 服务器要求额外的验证(如当前密码)
  2. 或者在前端实现验证码流程
async function transferMoney(amount, account) { // 先获取验证码 const { captchaId } = await http.get('/captcha'); const captcha = prompt('请输入验证码'); // 然后执行转账 return http.post('/transfer', { amount, account, captchaId, captcha }); }

7. 实际项目中的经验总结

在大型项目中应用CSRF保护时,我们积累了一些宝贵经验:

  1. Token命名一致性:

    • 确保前后端使用相同的Token名称(如XSRF-TOKEN)
    • 在微服务架构中,所有服务应该统一Token名称
  2. SPA应用的特别处理:

    • 在单页应用中,初始加载后可能不会刷新页面
    • 需要定期(如每小时)主动获取新Token
  3. 性能考虑:

    • Cookie的Path设置要合理,避免发送不必要的Cookie
    • 对于静态资源请求,服务器应省略Set-Cookie头
  4. 日志监控:

    http.interceptors.response.use(response => { if (response.headers['x-csrf-refreshed']) { log('CSRF Token已自动刷新'); } return response; });
  5. 框架集成建议:

    • 在Vue/React中,可以将axios实例挂载到全局属性
    • 提供useHttp这样的组合式API方便组件使用
// Vue3示例 app.config.globalProperties.$http = http; // React示例 const HttpContext = createContext(http); function App() { return ( <HttpContext.Provider value={http}> {/* 组件树 */} </HttpContext.Provider> ); }

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询