移动端自动填充触发表单自动提交致重复提交及CSRF报错问题
解决移动端表单自动填充后重复提交导致的CSRF错误问题
问题背景
启用CSRF防护的登录表单在安卓三星手机上出现重复提交问题:Chrome自动填充账号密码后会自动提交表单,用户未察觉提交状态再次点击提交按钮,导致CSRF令牌失效触发403错误,但后台实际已完成登录。桌面端无此问题,自动填充后需手动点击提交。
现有代码片段
HTML表单
<form action="/login/password" method="post"> <section> <label for="username">User Name :</label> <input id="username" name="username" type="text" autocomplete="username" required autofocus> </section> <section> <label for="current-password">Password :</label> <input id="current-password" name="password" type="password" autocomplete="current-password" required> </section> <input type="hidden" name="_csrf" value="<%= csrfToken %>"> <button class="btnSign" type="submit">Sign In</button> </form>
后端关键代码(Express + Passport)
router.use(function (req, res, next) { res.header("Cache-Control", "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0"); next(); }); passport.use( new LocalStrategy(function verify(username, password, cb) { conn.query("SELECT * FROM users WHERE username = ?", [username], function (err, row) { if (err) { return cb(err); } if (!row.length) { return cb(null, false, { message: "Incorrect username or password.", }); } if (locals.maxPasswordLength < password.length) { password = password.substring(0, Math.min(password.length, locals.maxPasswordLength)); } crypto.pbkdf2(password, row[0].salt, 310000, 64, "sha512", function (err, hashedPassword) { if (err) { return cb(err); } if (!crypto.timingSafeEqual(row[0].hashed_password, hashedPassword)) { return cb(null, false, { message: "Incorrect username or password.", }); } return cb(null, row[0]); }); }); }) ); router.post( "/login/password", passport.authenticate("local", { successReturnToOrRedirect: "/dothisonsuccess", failureRedirect: "/login", keepSessionInfo: false, failureMessage: true, }) ); router.get("/dothisonsuccess", function (req, res, next) { if (req.cookies.getSessionReturn) { res.clearCookie("getSessionReturn"); return res.redirect(req.cookies.getSessionReturn); } return res.redirect("/"); });
解决方案
不需要禁用自动填充,通过前后端配合控制防止重复提交:
1. 前端添加提交状态拦截
通过JavaScript监听表单提交事件,标记提交状态并禁用按钮,无论自动提交还是手动提交都会触发该逻辑:
const loginForm = document.querySelector('form[action="/login/password"]'); const submitBtn = loginForm.querySelector('.btnSign'); let isSubmitting = false; loginForm.addEventListener('submit', function(e) { if (isSubmitting) { e.preventDefault(); return; } isSubmitting = true; submitBtn.disabled = true; submitBtn.textContent = 'Signing In...'; // 可选:更新按钮文案提示状态 });
2. 后端添加登录状态前置检查
如果用户已经登录,直接跳转到成功页面,无需处理重复登录请求,避免CSRF令牌失效报错:
router.post( "/login/password", (req, res, next) => { if (req.isAuthenticated()) { return res.redirect('/dothisonsuccess'); } next(); }, passport.authenticate("local", { successReturnToOrRedirect: "/dothisonsuccess", failureRedirect: "/login", keepSessionInfo: false, failureMessage: true, }) );
3. 可选:优化自动填充触发逻辑
若自动提交仍频繁出现,可尝试给表单添加autocomplete="on"明确允许自动填充,同时确保表单验证逻辑仅在用户交互或提交时触发,避免浏览器误判提交条件。
关键原理
- 前端通过状态变量拦截所有重复提交请求,无论触发源是自动填充还是用户点击
- 后端提前校验登录状态,避免已登录用户重复提交引发的CSRF令牌失效问题
内容的提问来源于stack exchange,提问作者Anchar
相关产品推荐
相关产品推荐

