React表单提交时验证码验证总是失败怎么办?
我在开发用户重置密码功能时,用React写了带验证码的表单,但每次提交后端都返回验证码错误。明明前端生成的验证码和输入的一致,这是为什么呢?
我按照教程在后端生成验证码图片后,把验证码文本存在session里。React代码是这样的:
handleReset = async (e) => {
e.preventDefault();
const { code, password } = this.state;
try {
await axios.post('/api/reset', { code, password });
alert('成功');
} catch (err) {
alert('验证码错误');
}
};
测试时明明输入了正确的验证码,但后端始终验证不通过。我检查了后端session确实存了验证码,也确认了请求能到对应路由。难道是CSRF防护的问题?因为项目用了CsrfProtection中间件,但表单里已经加了csrfToken隐藏字段…
我们一步一步来排查和解决。
第一步,检查浏览器的网络请求里有没有自动带上 Cookie。React 用 axios 发请求,默认是不会带凭据(credentials)的,也就是 Cookie 不会随请求发送,这就导致后端虽然设置了 session,但每次请求都生成了新的 session ID,拿不到之前存的验证码。
你需要显式告诉 axios 要带上凭证。修改你的 axios 配置:
或者在具体请求里设置:
这个很重要。不然即使后端用了 session,前端每次请求也像“陌生人”一样,服务端只好给分配个新 session,里面的验证码自然对不上。
第二步,确认后端生成验证码的时候确实写进了 session,并且你在提交时是从同一个 session 读取。举个简单的 Express + express-session 的例子:
然后在验证的时候:
这里有两个细节你容易忽略:一是比对验证码时最好转成小写,避免用户输入大写出问题;二是用完一定要清 session,否则可以重复提交,有安全风险。
第三步,关于你提到的 CSRF 中间件。如果你用了 CSRF protection,比如 csurf 或者 express-csrf,那你不仅要传 csrfToken 字段,还得确保这个 token 和 session 是配套的。也就是说,如果前面没开 withCredentials,token 拿的是 A 会话的,提交时却到了 B 会话,那当然验证不过。
所以建议先暂时关掉 CSRF 中间件做测试,排除干扰。等验证码流程跑通了再加回来。加回来之后,前端需要先请求一次获取 csrfToken,存在 state 里,然后提交时一起发过去:
最后补充一点调试技巧:在后端打印 sessionID 和验证码值,前端也打一下你输入的值,看看是不是真的一致。
比如在 /api/reset 接口开头加:
console.log('sessionID:', req.sessionID);
console.log('server captcha:', req.session.captcha);
console.log('user input:', code);
然后刷新页面,走一遍流程,看日志里的 sessionID 是否稳定不变。如果每次都不一样,那就是 cookie 没传成功,问题出在前端请求配置或后端 session 配置。
总结一下你应该做的:
1. 在 axios 请求中加上 withCredentials: true
2. 确保后端 session 配置正确,允许跨域携带 cookie(如果是前后端分离部署)
3. 验证码比对时忽略大小写,验证后立即清除 session 中的验证码
4. 暂时关闭 CSRF 测试核心流程,避免干扰判断
5. 打日志看 sessionID 和验证码实际值,确认上下文一致性
我之前也踩过这坑,看着前端输入没错,结果是 session 对不上,折腾半天才发现是漏了 withCredentials。这种问题看着玄学,其实都是 HTTP 机制没配对上。一步步来,肯定能搞定。
先看看是不是这种情况:用户刷新验证码图片的时候,后端生成了新的验证码并存到session里,但前端拿到的是旧的验证码图片,所以导致不匹配。你可以试试在前端每次刷新验证码时,确保拉取到最新的一组验证码数据。
另外一种可能是前后端的大小写处理不一致,比如前端输入的是大写,但后端存的是小写。虽然你说了检查过文本一致,但还是建议加个
toLowerCase()统一处理下。如果你用了CSRF防护,虽然表单里加了csrfToken字段,但还是要确认一下axios请求头是否正确带上了CSRF Token。可以试着在axios拦截器里加上:
不过我觉得更大的可能是验证码刷新不同步的问题,重点查一下这块逻辑。