React表单提交如何防止CSRF攻击?隐藏字段没生效?
在React项目里做订单取消功能,用隐藏字段传CSRF令牌,但提交时后端返回403错误。代码是这样写的:
function CancelOrderForm() {
const [csrfToken, setCsrfToken] = useState('');
useEffect(() => {
fetch('/api/csrf-token')
.then(res => res.json())
.then(data => setCsrfToken(data.token));
}, []);
const handleSubmit = (e) => {
e.preventDefault();
fetch('/orders/cancel', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
orderId: '12345',
_csrf: csrfToken
})
});
};
return (
<form onSubmit={handleSubmit}>
<input type="hidden" name="_csrf" value={csrfToken} />
<button type="submit">取消订单</button>
</form>
);
}
后端要求同时携带Cookie里的CSRF令牌和表单里的隐藏字段,但请求头没有自动带Cookie。直接用Postman测试时如果少了Cookie里的token就会被拦截,但前端这样写哪里有问题?
fetch发请求时没带上 Cookie,导致后端收不到 Cookie 里的 CSRF 令牌,而你表单里的隐藏字段又没被自动序列化进请求体——fetch不会像原生 form 提交那样自动把 input 值打包进 body。你现在的代码里 body 是自己写的 JSON,只传了
orderId和_csrf,但没带 Cookie;而后端要求「Cookie 里的 CSRF token」和「请求体里的 _csrf 字段」都得有,所以 403。推荐的做法是:
1. 在
fetch请求里加上credentials: 'include',让浏览器自动带 Cookie 过去2. 后端如果用的是 Express + csurf,通常默认会从 Cookie 和 body 同时校验,但你得确保 Cookie 确实被设置了(比如后端返回的 Set-Cookie 没被浏览器拦截)
改完的代码应该是这样:
另外你隐藏字段
<input type="hidden" name="_csrf" />在fetch请求里根本没用,因为 fetch 不会自动读表单字段,它只在用FormData或原生 form 提交时才生效。如果你真想用表单自动提交,可以改用FormData或直接提交<form>,但现代 React 项目一般还是自己拼 body,所以重点是credentials: 'include'。顺便一提,别忘了后端要配置
Access-Control-Allow-Credentials: true,否则跨域时 Cookie 也不会发出去。credentials: 'include'。默认情况下,fetch 不会自动带上 Cookie,而后端又依赖 Cookie 里的 CSRF 令牌来验证请求。你的代码需要调整的地方不多,重点是在 fetch 请求中加上这个配置。另外,隐藏字段其实在这里不是必须的,因为后端更关注的是 Cookie 和请求体里的 CSRF 令牌是否匹配。以下是修改后的代码:
另外需要注意的是,如果你的前端和后端不在同一个域名下,比如前端运行在
localhost:3000而后端在api.example.com,还需要确保后端正确设置了 CORS 策略,允许前端域名访问,并且支持credentials。后端的响应头应该包含类似这样的配置:如果后端没有正确设置 CORS,浏览器会直接拦截跨域请求,连带 Cookie 都不会发送。你可以先用浏览器开发者工具检查网络请求,看看是不是跨域问题导致的。
最后吐槽一句,CSRF 这种东西每次调试都挺烦人的,尤其是前后端分离的项目,搞不好就会被安全机制拦住。希望这些调整能帮你解决问题。