使用 Express 处理表单的最佳实践
Best practices form processing with Express
我正在编写一个实现用户管理系统的网站,我想知道我必须考虑哪些关于表单处理的最佳实践。
尤其是性能、安全性、SEO 和用户体验对我来说很重要。当我处理它时,我遇到了几个问题,但我没有找到完整的 node/express 代码片段,我可以在其中找出我下面所有的问题。
用例:有人要更新他个人资料的生日。现在我正在向同一个 URL 发出 POST 请求以处理该页面上的表单,并且 POST 请求将以 302 重定向响应到同一个 URL。
关于表单处理的一般问题:
- 我应该执行 POST 请求 + 302 重定向以进行表单处理还是执行其他类似 AJAX 请求的操作?
- 我应该如何处理无效的 FORM 请求(例如无效的登录名,或电子邮件地址在注册期间已被使用)?
表达表单处理的具体问题:
我假设在将任何内容插入我的数据库之前,我需要清理并验证服务器端的所有表单字段。你会怎么做?
我阅读了一些关于 CSRF 的内容,但我从未实施过 CSRF 保护。我也很高兴在代码片段中看到这一点
使用 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
}
注意: 非常感谢一个完整的代码片段,它负责适应给定示例的所有表单处理最佳实践。我可以使用任何公开可用的 express 中间件。
我当然不是快递专家,但我想我至少可以回答#1:
您应该遵循 Post/Redirect/Get 网络开发模式,以防止重复提交表单。我听说 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();
我无法发表评论,因此即使答案不是完整的答案。只是觉得它可能对你有帮助。
如果您正在考虑用户体验,那么页面重定向是一个强烈的否定。为访问您网站的人提供顺畅的流程对于防止掉线很重要,并且由于填写表格已经不是一件令人愉快的事情,因此简化他们的使用是首要的。您不想重新加载他们的页面,该页面可能已经花费了一些时间来加载只是为了显示错误消息。一旦表单有效并且您创建了用户 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 中,但这种情况仍然很少见。
Should I do a POST request + 302 redirect for form processing or rather something else like an AJAX request?
否,自 2004 年左右以来(基本上自从 gmail 推出以来)良好用户体验的最佳实践一直是通过 AJAX 提交表单,而不是完整的 web 1.0-页面加载表单 POSTs。特别是,通过 AJAX 进行的错误处理不太可能让您的用户陷入浏览器错误页面的死胡同,然后通过后退按钮遇到问题。在这种情况下,AJAX 应该发送 HTTP PATCH 请求以在语义上最正确,但 POST 或 PUT 也将完成工作。
How should I handle invalid FORM requests (for example invalid login, or email address is already in use during signup)?
无效的用户输入应导致 HTTP 400 Bad Request
状态代码响应,并在 JSON 响应正文中包含有关特定错误的详细信息(格式因应用程序而异,但一般消息或逐字段错误是常见的主题)
对于已在使用的电子邮件,我使用 HTTP 409 Conflict
状态代码作为一般错误请求负载的更特殊形式。
I assume before inserting anything into my DB I need to sanitize and validate all form fields on the server side. How would you do that?
当然可以。有很多工具。我通常在 JSON Schema and use a library from npm to validate that such as is-my-json-valid or ajv 中为有效请求定义模式。特别是,我建议尽可能严格:拒绝不正确的类型,如果必须的话强制转换类型,删除意外的属性,尽可能使用小但合理的字符串长度限制和严格的字符串正则表达式模式,当然要确保你的数据库库 属性 防止注入攻击。
I read some things about CSRF but I have never implemented a CSRF protection.
OWSAP Node Goat Project CSRF Exercise 是从易受攻击的应用程序开始,了解并利用漏洞,然后实施修复(在本例中直接集成 express.csrf()
中间件。
Do I need to take care of any other possible vulnerabilities when processing forms with Express?
是 通常,应用程序开发人员必须了解并积极地安全编码。这方面有很多 material,但当用户输入涉及数据库查询、子进程生成或被写回 HTML 时,必须特别小心。可靠的查询库和模板引擎将处理这里的大部分工作,您只需要了解恶意用户输入可能潜入的机制和潜在位置(如图像文件名等)。
我正在编写一个实现用户管理系统的网站,我想知道我必须考虑哪些关于表单处理的最佳实践。
尤其是性能、安全性、SEO 和用户体验对我来说很重要。当我处理它时,我遇到了几个问题,但我没有找到完整的 node/express 代码片段,我可以在其中找出我下面所有的问题。
用例:有人要更新他个人资料的生日。现在我正在向同一个 URL 发出 POST 请求以处理该页面上的表单,并且 POST 请求将以 302 重定向响应到同一个 URL。
关于表单处理的一般问题:
- 我应该执行 POST 请求 + 302 重定向以进行表单处理还是执行其他类似 AJAX 请求的操作?
- 我应该如何处理无效的 FORM 请求(例如无效的登录名,或电子邮件地址在注册期间已被使用)?
表达表单处理的具体问题:
我假设在将任何内容插入我的数据库之前,我需要清理并验证服务器端的所有表单字段。你会怎么做?
我阅读了一些关于 CSRF 的内容,但我从未实施过 CSRF 保护。我也很高兴在代码片段中看到这一点
使用 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
}
注意: 非常感谢一个完整的代码片段,它负责适应给定示例的所有表单处理最佳实践。我可以使用任何公开可用的 express 中间件。
我当然不是快递专家,但我想我至少可以回答#1:
您应该遵循 Post/Redirect/Get 网络开发模式,以防止重复提交表单。我听说 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();
我无法发表评论,因此即使答案不是完整的答案。只是觉得它可能对你有帮助。
如果您正在考虑用户体验,那么页面重定向是一个强烈的否定。为访问您网站的人提供顺畅的流程对于防止掉线很重要,并且由于填写表格已经不是一件令人愉快的事情,因此简化他们的使用是首要的。您不想重新加载他们的页面,该页面可能已经花费了一些时间来加载只是为了显示错误消息。一旦表单有效并且您创建了用户 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 中,但这种情况仍然很少见。
Should I do a POST request + 302 redirect for form processing or rather something else like an AJAX request?
否,自 2004 年左右以来(基本上自从 gmail 推出以来)良好用户体验的最佳实践一直是通过 AJAX 提交表单,而不是完整的 web 1.0-页面加载表单 POSTs。特别是,通过 AJAX 进行的错误处理不太可能让您的用户陷入浏览器错误页面的死胡同,然后通过后退按钮遇到问题。在这种情况下,AJAX 应该发送 HTTP PATCH 请求以在语义上最正确,但 POST 或 PUT 也将完成工作。
How should I handle invalid FORM requests (for example invalid login, or email address is already in use during signup)?
无效的用户输入应导致 HTTP 400 Bad Request
状态代码响应,并在 JSON 响应正文中包含有关特定错误的详细信息(格式因应用程序而异,但一般消息或逐字段错误是常见的主题)
对于已在使用的电子邮件,我使用 HTTP 409 Conflict
状态代码作为一般错误请求负载的更特殊形式。
I assume before inserting anything into my DB I need to sanitize and validate all form fields on the server side. How would you do that?
当然可以。有很多工具。我通常在 JSON Schema and use a library from npm to validate that such as is-my-json-valid or ajv 中为有效请求定义模式。特别是,我建议尽可能严格:拒绝不正确的类型,如果必须的话强制转换类型,删除意外的属性,尽可能使用小但合理的字符串长度限制和严格的字符串正则表达式模式,当然要确保你的数据库库 属性 防止注入攻击。
I read some things about CSRF but I have never implemented a CSRF protection.
OWSAP Node Goat Project CSRF Exercise 是从易受攻击的应用程序开始,了解并利用漏洞,然后实施修复(在本例中直接集成 express.csrf()
中间件。
Do I need to take care of any other possible vulnerabilities when processing forms with Express?
是 通常,应用程序开发人员必须了解并积极地安全编码。这方面有很多 material,但当用户输入涉及数据库查询、子进程生成或被写回 HTML 时,必须特别小心。可靠的查询库和模板引擎将处理这里的大部分工作,您只需要了解恶意用户输入可能潜入的机制和潜在位置(如图像文件名等)。