【问题标题】:Best practices form processing with Express使用 Express 处理表单的最佳实践
【发布时间】:2017-05-11 17:35:41
【问题描述】:

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

尤其是性能、安全性、搜索引擎优化和用户体验对我来说很重要。当我研究它时,我遇到了几个问题,但我没有找到完整的节点/快速代码 sn-p 在那里我可以弄清楚我下面的所有问题。

用例:有人要更新他的个人资料的生日。现在我正在向同一个 URL 发出 POST 请求以处理该页面上的表单,并且 POST 请求将响应 302 重定向到同一个 URL。

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

  1. 我应该为表单处理执行 POST 请求 + 302 重定向还是其他类似 AJAX 请求的操作?
  2. 我应该如何处理无效的 FORM 请求(例如无效的登录,或注册期间电子邮件地址已被使用)?

表达有关表单处理的具体问题:

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

  2. 我阅读了一些关于 CSRF 的内容,但我从未实施过 CSRF 保护。我也很高兴在代码 sn-p 中看到这一点

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

示例 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
}

注意:非常感谢一个完整的代码 sn-p,它负责所有适用于给定示例的表单处理最佳实践。我可以使用任何公开可用的快速中间件。

【问题讨论】:

    标签: node.js security express body-parser


    【解决方案1】:

    我应该为表单处理执行 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。特别是,我建议尽可能严格:拒绝不正确的类型,或者如果必须强制类型,删除意外属性,尽可能使用小但合理的字符串长度限制和严格的字符串正则表达式模式,当然还要确保你的数据库库属性可防止注入攻击。

    我阅读了一些关于 CSRF 的内容,但我从未实施过 CSRF 保护。

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

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

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

    【讨论】:

    • 感谢您抽出宝贵时间,您介意展示一个真实世界的代码示例来演示您如何清理和验证表单吗?这将有助于我理解你的方法。如果您想知道,我正在使用猫鼬。
    • 我非常不喜欢猫鼬,所以很抱歉。
    【解决方案2】:

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

    您应该遵循 Post/Redirect/Get Web 开发模式,以防止重复提交表单。我听说 303-redirect 是用于重定向表单提交的正确 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();
    

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

    【讨论】:

      【解决方案3】:

      如果您正在考虑用户体验,那么页面重定向是绝对不行的。为访问您网站的人提供顺畅的流程对于防止丢失很重要,并且由于表单已经不那么令人愉快,因此简化它们的使用是主要的。您不想重新加载他们可能已经花费一些时间来加载的页面,只是为了显示错误消息。一旦表单有效并且您创建了用户 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-day,例如 the one 2015 年发生在 JWT 上,但这种情况仍然很少见。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-04-08
        • 1970-01-01
        • 1970-01-01
        • 2013-08-25
        • 1970-01-01
        • 1970-01-01
        • 2015-12-02
        相关资源
        最近更新 更多