为什么我的CSRF令牌验证总是失败?
我在表单提交里用了同步令牌防护,前端生成token存到cookie和隐藏字段,但提交后后端一直报错说令牌无效,这是哪里出错了呢?
我的实现是这样的:每次页面加载时用JavaScript生成随机字符串存到cookie和表单里,提交时通过AJAX把cookie里的token和表单里的token一起发送。但后端验证时两个值总对不上:
// 生成令牌的代码
function generateToken() {
const token = Math.random().toString(36).substr(2, 10);
document.cookie = `csrfToken=${token}; path=/`;
document.getElementById('csrfField').value = token;
}
// 表单提交时的AJAX
document.querySelector('form').addEventListener('submit', (e) => {
e.preventDefault();
const formToken = new FormData(e.target).get('csrfToken');
fetch('/api/submit', {
method: 'POST',
headers: { 'X-CSRF-Token': document.cookie }, // 这里有问题吗?
body: JSON.stringify({ ...formValues, csrfField: formToken })
});
});
后端PHP验证代码是直接对比$_COOKIE[‘csrfToken’]和POST[‘csrfField’],但每次都是不匹配。明明前端同时设置了cookie和表单字段,为什么会出现这种不一致?
### 1. 先说说为什么会不一致
你在前端生成了一个随机字符串,同时存到了 cookie 和表单隐藏字段中。但问题是,
document.cookie这个东西有点复杂。当你设置document.cookie = 'csrfToken=xxx'的时候,浏览器会自动管理这个 cookie 的生命周期,包括路径、域名等信息。如果你没有显式指定这些属性,可能会导致一些意外行为。另外,AJAX 请求中的
headers部分,你直接用了document.cookie,但实际上document.cookie返回的是 **所有** 当前域名下的 cookie 字符串(格式是key1=value1; key2=value2),而不是单个csrfToken的值。这就导致后端接收到的X-CSRF-Token头部可能包含了其他无关的 cookie 数据,而不是你想要的那个 token。### 2. 解决方案
我们需要确保以下几点:
- 前端生成的 token 能正确地传递到后端。
- 后端能够正确解析并验证这个 token。
#### 第一步:改进前端代码
我们先来看一下如何更可靠地从 cookie 中提取出具体的 token 值。可以写一个简单的函数来获取指定名称的 cookie 值:
重点在于
getCookie函数,它能帮你从document.cookie中准确提取出csrfToken的值,而不是把所有的 cookie 都扔过去。#### 第二步:改进后端验证逻辑
后端 PHP 需要验证两个地方的 token 是否一致:一个是请求头中的
X-CSRF-Token,另一个是 POST 数据中的csrfField。我们可以稍微调整一下验证逻辑:这样写的好处是,我们明确指定了从哪里获取 token,并且用严格相等 (
===) 来比较它们是否完全一致。### 3. 原理解释
为什么之前会出问题?主要是因为:
-
document.cookie返回的是所有 cookie 的字符串,而不是单独的一个值。- 如果请求头中的
X-CSRF-Token包含了多余的 cookie 数据,后端在解析时就会出现问题。- 同时,如果 cookie 的路径或域名设置不对,也可能导致前端读取不到正确的 token。
通过上面的改进,我们确保了:
1. 前端正确提取并发送了
csrfToken。2. 后端能够准确验证这两个值。
### 4. 小贴士
最后再提醒一下,CSRF 保护虽然很重要,但也别忘了结合 HTTPS 使用,否则 token 在传输过程中可能会被截获。另外,生成 token 的方式也可以考虑使用更强的随机数生成方法,比如
,这样安全性会更高一些。crypto.getRandomValues()希望这些解释能帮到你!如果有其他问题随时问我。