仅Chrome扩展中Django POST请求出现CSRF验证失败问题
我来帮你分析下这个问题——Chrome扩展环境发起跨域请求时,默认不会自动携带Referer头,这正是服务器返回CSRF Failed: Referer checking failed - no Referer的核心原因。而你提到的其他场景(localhost、本地HTML、终端)都会自动生成合法的Referer,所以请求能正常通过。
先回顾下你的问题场景:
- 你用来发起POST请求的代码:
var session_id; // to use for token based authentication // prep $(document).ready(function(){ // sending a csrftoken with every ajax request function csrfSafeMethod(method) { // these HTTP methods do not require CSRF protection return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); } if (!chrome.cookies) { chrome.cookies = chrome.experimental.cookies; } const csrf_from_cookies = {'url': 'https://dummy-site-xyz.com/', 'name': 'csrftoken'}; chrome.cookies.get(csrf_from_cookies, function(res){ csrftoken = res.value; $.ajaxSetup({ beforeSend: function(xhr, settings) { if (!csrfSafeMethod(settings.url)) { xhr.setRequestHeader("X-CSRFToken", csrftoken); } } }); }); const sessionid = {'url': 'https://dummy-site-xyz.com/', 'name': 'sessionid'}; chrome.cookies.get(sessionid, function(res) { session_id = res.value; }); }); // the request $.ajax({ type: "POST", crossDomain: true, url: 'https://dummy-site-xyz.com/profile/api/v1/awesome/key', data: {'sessionid': 'dummy_session_id'}, success: function(data){ // pass }, error: function( jqXHR, textStatus, errorThrown ){ console.log(jqXHR.responseJSON); } });
- 你的
manifest.json权限配置:
"permissions": [ . . . "https://dummy-site-xyz.com/*", . . ],
下面是具体的解决步骤:
1. 手动添加Referer头到请求中
Chrome扩展允许手动设置Referer头,只要目标站点在你的权限列表里。修改你的AJAX请求,添加headers字段:
$.ajax({ type: "POST", crossDomain: true, url: 'https://dummy-site-xyz.com/profile/api/v1/awesome/key', headers: { 'Referer': 'https://dummy-site-xyz.com/' // 必须匹配目标站点的域名 }, data: {'sessionid': session_id}, // 注意:这里要用实际获取到的session_id,不是硬编码的'dummy_session_id'! success: function(data){ // 处理返回数据 }, error: function( jqXHR, textStatus, errorThrown ){ console.log(jqXHR.responseJSON); } });
2. 修复异步操作的时序问题
你当前的代码中,获取csrftoken和sessionid是异步操作,但AJAX请求直接在外部发起——这很可能导致请求发起时,这两个变量还没被赋值!
建议用Promise封装cookie获取逻辑,确保拿到所有必要参数后再发起请求:
// 封装获取cookie的Promise function getCookie(name) { return new Promise((resolve, reject) => { chrome.cookies.get({url: 'https://dummy-site-xyz.com/', name: name}, (res) => { res ? resolve(res.value) : reject(new Error(`Cookie ${name} not found`)); }); }); } // 等待两个cookie都获取完成后再发请求 $(document).ready(function(){ Promise.all([getCookie('csrftoken'), getCookie('sessionid')]) .then(([csrftoken, session_id]) => { // 配置全局AJAX的CSRF头 $.ajaxSetup({ beforeSend: function(xhr, settings) { if (!/^(GET|HEAD|OPTIONS|TRACE)$/.test(settings.type)) { xhr.setRequestHeader("X-CSRFToken", csrftoken); } } }); // 发起POST请求 $.ajax({ type: "POST", crossDomain: true, url: 'https://dummy-site-xyz.com/profile/api/v1/awesome/key', headers: { 'Referer': 'https://dummy-site-xyz.com/' }, data: {'sessionid': session_id}, success: function(data){ console.log('获取密钥成功:', data); }, error: function( jqXHR, textStatus, errorThrown ){ console.log(jqXHR.responseJSON); } }); }) .catch(err => console.error('获取Cookie失败:', err)); });
3. 调整Manifest配置(可选但推荐)
如果是Chrome 88及以上版本,推荐用host_permissions替代permissions来声明站点权限,同时可以配置内容安全策略(CSP)确保Referer头正常发送:
{ "manifest_version": 3, // 如果你用的是V3版本 "host_permissions": [ "https://dummy-site-xyz.com/*" ], "content_security_policy": { "extension_pages": "script-src 'self'; object-src 'none'; referrer 'strict-origin-when-cross-origin'" } }
strict-origin-when-cross-origin是较安全的Referer策略,既符合服务器校验要求,又不会泄露过多URL信息。如果服务器不接受这个值,可以换成unsafe-url(但不推荐,会发送完整URL)。
4. 尝试用原生Fetch API替代jQuery AJAX(可选)
原生Fetch在Chrome扩展环境中的头处理更灵活,还能利用credentials: 'include'自动携带cookie,省去手动传递sessionid的步骤:
function getCookie(name) { return new Promise((resolve, reject) => { chrome.cookies.get({url: 'https://dummy-site-xyz.com/', name: name}, (res) => { res ? resolve(res.value) : reject(new Error(`Cookie ${name} not found`)); }); }); } $(document).ready(function(){ getCookie('csrftoken') .then(csrftoken => { fetch('https://dummy-site-xyz.com/profile/api/v1/awesome/key', { method: 'POST', headers: { 'X-CSRFToken': csrftoken, 'Referer': 'https://dummy-site-xyz.com/', 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ 'sessionid': session_id }), credentials: 'include' // 自动携带目标站点的cookie }) .then(res => res.json()) .then(data => console.log('获取密钥成功:', data)) .catch(err => console.error('请求失败:', err)); }) .catch(err => console.error('获取CSRF Token失败:', err)); });
按照以上步骤调整后,应该就能解决Referer缺失导致的CSRF校验失败问题了。
内容的提问来源于stack exchange,提问作者Fallen

