我正在编写一个实现用户管理系统的网站,我想知道我必须考虑哪些关于表单处理的最佳实践。
特别是性能,安全性,SEO和用户体验对我来说非常重要。当我正在处理它时,我遇到了几个问题,我没有找到一个完整的节点/快速代码片段,我可以在其中弄清楚以下所有问题。
使用案例:有人会更新其个人资料的生日。现在我正在对同一个URL执行POST请求以处理该页面上的表单,POST请求将使用302重定向到同一URL进行响应。
有关表单处理的一般问题:
表达有关表单处理的具体问题:
我假设在将任何内容插入我的数据库之前,我需要清理并验证服务器端的所有表单字段。你会怎么做?
我读了一些关于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
}
注意:我们非常感谢完整的代码段,其中包含了适用于给定示例的所有表单处理最佳做法。我可以使用任何公开的快速中间件。
答案 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-valid或ajv等。特别是,我建议尽可能严格:拒绝不正确的类型,或强制类型,如果必须,删除意外的属性,使用小但合理的字符串长度限制和严格的正则表达式模式为字符串,当然,当然,请确保您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,但这种情况仍然很少见。