使用Express

时间:2017-05-11 17:35:41

标签: node.js security express body-parser

我正在编写一个实现用户管理系统的网站,我想知道我必须考虑哪些关于表单处理的最佳实践。

特别是性能,安全性,SEO和用户体验对我来说非常重要。当我正在处理它时,我遇到了几个问题,我没有找到一个完整的节点/快速代码片段,我可以在其中弄清楚以下所有问题。

使用案例:有人会更新其个人资料的生日。现在我正在对同一个URL执行POST请求以处理该页面上的表单,POST请求将使用302重定向到同一URL进行响应。

有关表单处理的一般问题:

  1. 我应该为表单处理执行POST请求+ 302重定向,还是像AJAX请求那样做其他事情?
  2. 我应该如何处理无效的FORM请求(例如注册时无效的登录或电子邮件地址已被使用)?
  3. 表达有关表单处理的具体问题:

    1. 我假设在将任何内容插入我的数据库之前,我需要清理并验证服务器端的所有表单字段。你会怎么做?

    2. 我读了一些关于CSRF的事情,但我从未实施过CSRF保护。我也很高兴在代码片段中看到它

    3. 使用Express处理表单时是否需要处理任何其他可能的漏洞?

    4. 示例HTML / Pug:

      form#profile(method='POST', action='/settings/profile')
          input#profile-real-name.validate(type='text', name='profileRealName', value=profile.name)
          label(for='profile-real-name') Name
      
          textarea#profile-bio.materialize-textarea(placeholder='Tell a little about yourself', name='profileBio')
              | #{profile.bio}
          label(for='profile-bio') About
      
          input#profile-url.validate(type='url', name='profileUrl', value=profile.bio)
          label(for='profile-url') URL
      
          input#profile-location.validate(type='text', name='profileLocation', value=profile.location)
          label(for='profile-location') Location
      
          .form-action-buttons.right-align
              a.btn.grey(href='' onclick='resetForm()') Reset
              button.btn.waves-effect.waves-light(type='submit')
      

      路由处理程序示例:

      router.get('/settings/profile', isLoggedIn, profile)
      router.post('/settings/profile', isLoggedIn, updateProfile)
      
      function profile(req, res) {
          res.render('user/profile', { title: 'Profile', profile: req.user.profile })
      }
      
      function updateProfile(req, res) {
          var userId = req.user._id
          var form = req.body
          var profile = {
              name: form.profileRealName,
              bio: form.profileBio,
              url: form.profileUrl,
              location: form.profileLocation
          }
      
          // Insert into DB
      }
      

      注意:我们非常感谢完整的代码段,其中包含了适用于给定示例的所有表单处理最佳做法。我可以使用任何公开的快速中间件。

3 个答案:

答案 0 :(得分:2)

  
    

我应该为表单处理执行POST请求+ 302重定向,还是像AJAX请求那样做其他事情?

  

没有,自2004年左右以来(从gmail推出以来)提供良好用户体验的最佳做法是通过AJAX提交表单而不是web 1.0整页加载表单POST。特别是,通过AJAX进行错误处理不太可能让您的用户处于死胡同浏览器错误页面,然后使用后退按钮点击问题。在这种情况下,AJAX应该发送HTTP PATCH请求,使其在语义上最正确,但POST或PUT也可以完成工作。

  
    

我应该如何处理无效的FORM请求(例如,在注册过程中无效登录或电子邮件地址已被使用)?

  

无效的用户输入应该导致HTTP 400 Bad Request状态代码响应,其中包含有关JSON响应正文中特定错误的详细信息(格式因应用程序而异,但要么是一般消息,要么是逐字段消息字段错误是常见的主题)

对于已经使用的电子邮件,我使用HTTP 409 Conflict状态代码作为一般错误请求有效负载的更具体的风格。

  
    

我假设在将任何内容插入我的数据库之前,我需要清理并验证服务器端的所有表单字段。你会怎么做?

  

绝对。有很多工具。我通常在JSON Schema中为有效请求定义架构,并使用npm中的库来验证is-my-json-validajv等。特别是,我建议尽可能严格:拒绝不正确的类型,或强制类型,如果必须,删除意外的属性,使用小但合理的字符串长度限制和严格的正则表达式模式为字符串,当然,当然,请确保您DB库属性可防止注入攻击。

  
    

我读了一些关于CSRF的事情,但我从未实施过CSRF保护。

  

OWSAP Node Goat Project CSRF Exercise是开始使用易受攻击的应用,了解并利用此漏洞,然后实施修复的好地方(在这种情况下,可以直接集成express.csrf()中间件。

  
    

使用Express处理表单时,是否需要处理任何其他可能的漏洞?

  

通常应用程序开发人员必须理解并安全地进行代码编写。这里有很多材料,但是当用户输入涉及数据库查询,子进程生成或写回HTML时,必须特别小心。固体查询库和模板引擎将处理大部分工作,您只需要了解恶意用户输入可能潜入的机制和潜在位置(如图像文件名等)。

答案 1 :(得分:1)

我当然不是快递专家,但我想我至少可以回答#1:

您应该遵循发布/重定向/获取Web开发模式,以防止重复提交表单。我听说303重定向是用于重定向表单提交的正确http状态代码。

我使用POST路由处理表单,一旦完成,我会触发302重定向。

从#3开始,我建议查看express-validator,这里有很好的介绍:https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/forms。它是一个允许您像这样验证和消毒的中间件:

req.checkBody('name', 'Invalid name').isAlpha();
req.checkBody('age', 'Invalid age').notEmpty().isInt();
req.sanitizeBody('name').escape();

即使它不是一个完整的答案,我也无法评论答案。只是觉得它可能对你有帮助。

答案 2 :(得分:1)

如果用户体验是您正在考虑的事情,那么页面重定向就是强烈的。为访问您网站的人们提供顺畅的流程对于防止丢弃非常重要,而且由于表格已经不是很容易填写,因此放宽使用是主要的。您不想重新加载可能已经花费一些时间加载的页面,只是为了显示错误消息。一旦表单有效并且您创建了用户cookie,重定向即可,即使您可以在客户端应用程序上执行操作以防止它,但这超出了范围。

正如Levent所说,你应该结帐express-validator,这是为这种目的而建立的解决方案。

req.check('profileRealName', 'Bad name provided').notEmpty().isAlpha()
req.check('profileLocation', 'Invalid location').optional().isAlpha();

req.getValidationResult().then(function (result) {
  if (result.isEmpty()) { return null }
  var errors = result.array()
  // [
  //   { param: "profileRealName", msg: "Bad name provided", value: ".." },
  //   { param: "profileLocation", msg: "Invalid location", value: ".." }
  // ]
  res.status(400).send(errors)
})
.then(function () {
  // everything is fine! insert into the DB and respond..
})

从它看起来,我可以假设你正在使用MongoDB。鉴于此,我建议使用ODM,如Mongoose。它允许您为模式定义模型并直接对其进行限制,让模型为您处理这些冗余验证。

例如,您的用户的模型可能是

var User = new Schema({
  name: { type: String, required: [true, 'Name required'] },
  bio: { type: String, match: /[a-z]/ },
  age: { type: Number, min: 18 }, // I don't know the kind of site you run ;)
})

在您的路线上使用此架构看起来像

var user = new User({
  name: form.profileRealName,
  bio: form.profileBio,
  url: form.profileUrl,
  location: form.profileLocation
})

user.save(function (err) {
  // and you could grab the error here if it exists, and react accordingly
});

正如你所看到的,它提供了一个非常酷的api,如果你想了解更多,你应该在their docs中阅读。

关于CRSF,您应该安装csurf,其中包含非常好的说明和示例用法。

在那之后你会非常好,除了确保你能及时了解你的关键依赖关系之外,还有更多可以考虑的事情,以防0天发生,例如{{发生在2015年的JWT,但这种情况仍然很少见。