You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

移动端自动填充触发表单自动提交致重复提交及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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 08:50:07