Express.js csurf报invalid csrf token错误及跨Django/Express适配问题
解决Express.js csurf「invalid csrf token」错误及跨Django/Express前端适配问题
一、Express csrf token无效问题排查与解决
1. 检查中间件顺序(核心!)
csurf 依赖 cookie 或 session 解析结果,必须放在cookie-parser、body 解析中间件之后,路由之前。错误顺序会直接导致 token 无法读取:
const express = require('express'); const cookieParser = require('cookie-parser'); const csurf = require('csurf'); const app = express(); // 正确顺序:先解析cookie app.use(cookieParser()); // 解析表单数据(表单提交必加) app.use(express.urlencoded({ extended: true })); // 解析JSON(AJAX JSON提交必加) app.use(express.json()); // 配置csurf,用cookie存储token(适合无session场景) const csrfProtection = csurf({ cookie: { httpOnly: false, // 允许前端JS读取cookie获取token(若用模板注入可设为true) secure: process.env.NODE_ENV === 'production', // 生产环境HTTPS下开启 sameSite: process.env.NODE_ENV === 'production' ? 'Strict' : 'Lax', path: '/' // 确保所有路由都能访问到cookie } }); // 给需要校验的路由组应用中间件(比如/admin下的所有路由) app.use('/admin', csrfProtection);
2. 确保token传递方式正确
根据请求类型,前端需对应传递token:
- 表单提交:添加隐藏字段
_csrf(字段名需与csurf配置一致,默认是_csrf)<form method="POST" action="/admin/upload"> <input type="hidden" name="_csrf" value="<%= csrfToken %>"> <!-- 其他表单字段 --> </form> - AJAX请求:将token放入
X-CSRF-Token请求头(或配置中指定的头名)// 从cookie提取token的工具函数 function getCsrfToken() { const match = document.cookie.match(/(^| )_csrf=([^;]+)/); return match ? match[2] : ''; } // 上传GIF的AJAX示例 async function uploadGif(formData) { const res = await fetch('/admin/upload', { method: 'POST', headers: { 'X-CSRF-Token': getCsrfToken() }, body: formData, credentials: 'include' // 必须携带cookie }); }
3. 跨域场景额外配置
如果前端与Express不在同一域名,需:
- 修改csurf的cookie配置:
sameSite: 'None', secure: true - 前端请求添加
credentials: 'include' - Express配置CORS允许携带凭证:
const cors = require('cors'); app.use(cors({ origin: 'https://your-frontend-domain.com', credentials: true }));
二、同一前端适配Django/Express方案
通过后端注入环境标识,前端动态适配CSRF规则与API地址:
1. 后端注入环境元信息
- Django模板:
<meta name="backend-env" content="django"> <meta name="csrf-token" content="{{ csrf_token }}"> - Express模板:
<meta name="backend-env" content="express"> <meta name="csrf-token" content="<%= csrfToken %>">
2. 前端统一请求逻辑
封装通用请求函数,根据环境自动切换CSRF头与API前缀:
// 初始化环境配置 const backendEnv = document.querySelector('meta[name="backend-env"]').content; const csrfTokenMeta = document.querySelector('meta[name="csrf-token"]'); let csrfToken = csrfTokenMeta ? csrfTokenMeta.content : ''; const apiBaseUrl = backendEnv === 'django' ? '/django-api' : '/express-api'; // 通用请求函数 async function sendRequest(url, options = {}) { const headers = { ...options.headers }; const method = (options.method || 'GET').toUpperCase(); // 非安全请求添加CSRF token if (!['GET', 'HEAD', 'OPTIONS'].includes(method)) { if (backendEnv === 'django') { headers['X-CSRFToken'] = csrfToken; // Django专用头 } else { headers['X-CSRF-Token'] = csrfToken || getCsrfToken(); // Express兼容两种获取方式 } } return fetch(`${apiBaseUrl}${url}`, { ...options, headers, credentials: 'include' // 携带cookie }); } // 工具函数:从cookie取Express的CSRF token function getCsrfToken() { const match = document.cookie.match(/(^| )_csrf=([^;]+)/); return match ? match[2] : ''; }
3. 动态适配表单属性
如果用表单提交,动态设置action和CSRF字段:
<form id="upload-form" method="POST" enctype="multipart/form-data"> <!-- 表单字段 --> </form> <script> const form = document.getElementById('upload-form'); form.action = `${apiBaseUrl}/upload`; // Express环境添加隐藏CSRF字段(若不用请求头) if (backendEnv === 'express') { const csrfInput = document.createElement('input'); csrfInput.type = 'hidden'; csrfInput.name = '_csrf'; csrfInput.value = csrfToken || getCsrfToken(); form.appendChild(csrfInput); } </script>
内容的提问来源于stack exchange,提问作者Maxym Matskiv
相关产品推荐
相关产品推荐

