如何防止在 ExpressJS 中重复提交表单

How to prevent double submitting forms in ExpressJS

我遇到了我网站上的人可以重复提交表单的问题。这不是数据库端的问题,因为记录是通过服务器端验证保存的。但是,重要的是我使任何其他请求无效,这样用户就不会被重复收费(因为收费发生在创建帐户之前)。我宁愿完全放弃请求而不是重定向,因为它真的会破坏用户体验,而放弃请求只会使过程 "appear" 更长,这是一个更好的折衷方案。

为了弄清楚,我也使用了post-redirect-get 模式,但它并没有解决这个问题,这实际上是最关键的问题,因为它是最常见的。为了 UI 的清洁,将实施客户端解决方案,但我不能相信用户不会禁用 javascript 或以某种方式篡改它。

我有一个使用库 csurf 的中间件解决方案,但效果不佳。

var protect_route = function(req, res, next){
  if(!req.session.unique_requests){
    req.session.unique_requests = [];
  }

  var found = _.find(req.session.unique_requests, function(token){
    return token === req.body._csrf;
  });

  if(!found){
   req.session.unique_requests.push(req.body._csrf);
   next();
  } else {
   next('route'); //drop the post, and skip anything else.
  }
};

var unlock_route = function(req, res, next){
  req.session.unique_requests = _.without(req.session.unique_requests, req.body._csrf);
};

我像这样将它附加到我的路线上:

router.post('/create', protect_route, function(req, res){
   // If it makes it in here, the route is protected and the 
   // request wasn't dropped.
});

router.get('/review', unlock_route, function(req, res){
   // The form will once again be able to be submitted.
});

不幸的是,即使调用了中间件,如果我按下创建按钮,它也会触发大量请求,每个请求都会尝试将令牌推送到 unique_requests,结果是它赢了'阻止任何推送的请求,直到一切都赶上。

我在这里做错了什么,我该如何解决?

编辑: 将 POST 的速率限制为 /create 似乎是一种可能性,尽管它看起来不像是 真实的 我想是解决方案。可能有用。

您应该能够通过使用这些中间件处理标志来简化它,如下所示:

var protect_route = function(req, res, next){
  if(!req.session.processing){
    req.session.processing = true; // set flag
    next(); // continue in route chain
  } else {
    res.status(400).send() // end request here with 400 status code, drop everything
  }
}

var unlock_route = function(req, res, next){
  req.session.processing = false; // unset flag
  next();
};
     by using a simple session counter and set it as needed  in this example of 
     adding a role name single field string unique in schema definition
     counter field name is   rolesubmitcounter
     ///////////////////////////////////////////
     router.get('/add',ensureAuthenticated, function(req,res){
     req.session.rolesubmitcounter=0;
     res.render('roles/role_add',{
     title:"add role",
     nrole: {roleName:""}
     });
    });
    // Add Submit POST Route
     router.post('/add', ensureAuthenticated, function(req, res){
     if (req.session.rolesubmitcounter ===0){
     var tt1 =req.body.roleName.toString().trim();
    if(tt1==="".trim()){
    req.session.rolesubmitcounter=0;
       req.flash('danger','Role not..added.....may be empty fields......');
        res.render('roles/role_add', {
        title:'Add role',
        nrole: {roleName:req.body.roleName}
       });
      }
      else{
    ///////////////////////////////////////
    req.session.rolesubmitcounter = 1;
   let nrole = new Role();
   nrole.roleName = req.body.roleName.toLowerCase().trim();
   ///////////////////////////////////////////
   nrole.save(function(err){
    if(err){
      req.session.rolesubmitcounter=0;
       req.flash('danger','Role not..added.....may be duplicate key..');
       res.render('roles/role_add',{
       nrole: nrole
        });
      } else {
       req.flash('success','Role Added');
       res.redirect('/roles/roleindex');
       }
    });
    }
    }else{
     req.flash('danger','double submission Detected.....click once slowly');
     res.redirect('/roles/roleindex');
    }

   });