如何正确构建 HTML 表单:安全性
这是涵盖创建 Web 表单各个方面的系列文章的最后一篇。每篇文章都可以单独阅读,但我按照最合理的顺序编写了它们。如果您还没有阅读其他文章,我建议您阅读一下。
- 第一部分:语义
- 第 2 部分:可访问性
- 第 3 部分:造型
- 第四部分:用户体验
- 第 5 部分:安全性
本系列的最后一篇文章可以说是最重要的一篇。它涵盖了安全性。虽然其他文章主要关注前端,但安全性远不止于此。我们必须考虑当前用户、其他用户以及我们自身的安全。因此,我们将从前端到后端,乃至更广阔的范围,全面审视整个应用程序架构。
加密流量(SSL)
在深入探讨之前,我将使用“SSL”一词来指代一种用于加密互联网流量的技术。严格来说,我指的是传输层安全性 (TLS),但“SSL”通常被理解为与TLS含义相同。它赋予了网站URL栏中的绿色小锁,也解释了为什么网站以“http* s *”而不是“http”(没有“s”)开头。
安装 SSL 证书是最佳做法,原因有很多,其中最重要的就是安全性。拥有 SSL 证书可以加密客户端(您的用户)和服务器(您)之间传输的数据。
监听网络的黑客可能会检查用户发送的数据包。如果没有 SSL 证书,这些数据很容易被读取为纯文本。即使有 SSL 证书,数据仍然可以被拦截,但会以加密文本的形式发送,这几乎毫无用处。
-
没有 SSL 证书:用户名:
NuggetTheMighty; password: ILoveSquirrels
-
使用 SSL 证书(加密):
SIUFJaYQNtsn+y73mfBYv3fVfjJ2GdHl4A7XnTJXxgUyd4/TrU3nN+g3aJ4BVXSJL/E7
在创建表单时,这一点尤为重要,因为表单的最终目的是发送数据。我们有责任保护用户的数据。
过去,获取和安装 SSL 证书需要花费时间、金钱和技术知识。如今,许多托管服务商会免费提供并安装证书。很多情况下,他们甚至会自动安装。
如果您的托管服务不提供 SSL 证书,还有其他选择:
- Cloudflare通过其 DNS 提供“灵活”的 SSL 服务。它免费且简单易用,但最好还是自己拥有一个。
- 如果您的网站使用WordPress ,则可以通过Let's Encrypt在一分钟内设置几个插件来设置证书。
- 您可以使用Certbot通过Let's Encrypt为您生成并安装证书。
如果您仍然没有 SSL 证书,因为您的主机商不提供证书,而且您无法控制 DNS 或服务器代码……那么,您就有点倒霉了。您得更换主机商,或者联系服务器管理员,或者采取其他措施,因为如今这对于任何项目来说都应该是硬性要求。
理解 GET 与 POST
在上一篇文章中,我提到过method
表单中应该始终包含属性。该method
属性指示表单在请求中使用GET
或POST
HTTP 标头提交数据。如果省略method
,浏览器将默认使用方法。这一点很重要,因为和请求GET
之间存在显著差异。GET
POST
GET 请求
看一下下面这个使用 GET 方法的表单。提交表单时,数据会被提交到 example.com(剧透一下,它实际上什么也没做)。
<form action="https://example.com" method="GET" target="_blank">
<label for="data">Send some sweet data</label>
<input id="data" name="some-sweet-data"/>
<button>Submit</button>
</form>
需要注意的关键点是提交表单后的 URL。虽然表单的 URLaction
是“example.com”,但提交 URL 却是“example.com?some-sweet-data=blahblahblah ”。这些查询参数对应于表单输入框的 name 属性。这就是使用该方法的表单传递数据的方式GET
:查询字符串参数。
将数据作为 URL 参数传递与安全性相关,原因如下:
- 对于许多用户来说,URL 会保存在浏览器的历史记录中。试想一下,如果表单通过查询参数发送了信用卡号,而用户使用的是公共计算机,例如图书馆。他们的私人数据最终可能会被保存在浏览器的历史记录中,供下一个用户查看。
- 许多服务器会记录接收流量的 URL 日志。如果敏感信息最终出现在服务器日志文件中,任何有权访问这些文件的人都可以看到这些数据。
POST 请求
幸运的是,你可以使用该POST
方法发送数据,而无需使用查询参数。让我们看一下相同的表单,但使用该POST
方法:
<form action="https://example.com" method="POST" target="_blank">
<label for="data">Send some sweet data</label>
<input id="data" name="some-sweet-data"/>
<button>Submit</button>
</form>
请注意,提交此表单也会加载 example.com,但这次查询参数中没有任何内容。这是因为在请求中,数据是作为请求POST
正文的一部分发送的。这使得意外泄露私人信息的难度更大。
出于上述原因,我通常会在所有表单上使用该方法。但也有例外,比如我想让用户将表单提交的内容添加到书签中,或者分享给其他人。例如,看看这个向DuckDuckGoPOST
提交搜索信息的表单:
<form action="https://duckduckgo.com/" method="GET" target="_blank">
<label for="query">Search</label>
<input id="query" name="q"/>
<button>Submit</button>
</form>
表单提交后,DuckDuckGo 会打开 URL,其中包含一个查询参数。之后,您可以根据需要复制此 URL 并与同事分享,或者将其添加到书签中以供日后使用。除非您处理的是敏感数据,否则记住这个模式非常有用。
防止垃圾邮件
没人喜欢垃圾邮件。我承认,它跟安全关系不大。这里值得一提,因为每当我们在公共网页上添加表单时,就等于为垃圾邮件打开了大门。表单是用来填写的,但有时它们会被某些人或某些*东西*出于恶意而填写。
那么我们该如何预防呢?
蜜罐
防止垃圾邮件的一种基本方法叫做“蜜罐”,其概念非常简单。如果你在表单中包含一个隐藏的输入框,你就知道真正的人永远不会修改该字段。因此,如果提交的表单中包含了该输入框的数据,你就可以认定它是机器人提交的,并拒绝该提交。
实际上,输入可能如下所示:
- 这
name
很重要,这样你就知道在后端需要检查什么了。我用的是“蜜罐”,但大多数人建议用一个听起来更合法的名字。 - 我使用了一个
visually-hidden
类来隐藏用户的输入(您可以在有关可访问性或样式的文章中阅读更多相关信息)。机器人仍然可以看到它。 - 这
tabindex="-1"
移除了键盘导航的输入功能。这对于辅助技术用户来说非常重要(更多信息请参阅无障碍文章)。 - 最后,我们希望阻止浏览器表单自动填充输入,因此我们禁用
autocomplete
。
这种方法的好处是,实施起来几乎不需要花费时间和精力。坏消息是,很多机器人足够聪明,能够分辨出哪些输入是蜜罐,并会跳过它。不过,即使这种方法只能拦截 10% 的垃圾邮件,付出的努力也是值得的。
安全挑战
防止垃圾邮件的更可靠方法是添加一个挑战,要求用户完成该挑战以证明自己是人类。一些基本的例子是要求您完成一个简单的数学问题,例如“10 + 6 等于多少?”。只有答案正确的数据才会被接受。
这种方法的问题在于,机器人足够复杂,可以再次解决这些挑战。
垃圾邮件困境就像一场不断演变的猫捉老鼠游戏,随着时间的推移,挑战也变得越来越复杂。先是数学问题,然后是图像中的字母或数字识别。
最广为人知的安全验证或许是reCAPTCHA了。这是谷歌旗下的一项服务,它会向用户展示一系列需要识别的图像。它运行良好,而且免费。如果您担心用户隐私,可能不想使用谷歌产品。好消息是,还有另一项名为hCaptcha的服务可以作为替代方案。安全验证技术并非没有缺点:
- 它们的实施需要更多的技术。
- 您可能需要依赖第三方服务。
- 它们会对用户体验产生负面影响。
WAF 和 API
如果垃圾邮件成为您表单中的一个主要问题,您可能需要考虑寻求第三方服务。
一种选择是设置Web 应用程序防火墙 (WAF)。WAF 位于服务器前端,可以阻止恶意攻击者的流量访问您的网站。
Cloudflare是我首选的供应商。他们提供 DNS 级别的服务,并且提供非常慷慨的免费套餐。我拥有的每个域名都使用 Cloudflare,到目前为止,我还没有遇到过任何垃圾邮件问题。
另一个选择是使用 API 服务来测试传入的表单提交。我所知道的最常见的 API 是Akismet ,它是Automattic产品(他们开发了WordPress )的一部分。我在一些 WordPress 网站上使用过它,效果很好。如果你不使用 WordPress,他们也提供 API。如果你对其他选项感兴趣,CSS Tricks 有一篇文章更深入地介绍了第三方垃圾邮件 API。
我不会相信任何垃圾邮件防御技术能 100% 保证万无一失。这个领域不断发展,垃圾邮件发送者的技术也一年比一年先进。然而,除非我找到了解决方案,否则我不会主动尝试解决这类问题。在这种情况下,你可以先从一些容易实现的方案入手,逐步深入到更复杂的解决方案。
考虑到努力程度、用户体验、成本以及其他所有因素,我会采取如下方法:
- 在您的 DNS(或其他 WAF)上设置 Cloudflare
- 使用蜜罐
- 集成垃圾邮件检测 API
- 设置 hCaptcha(出于用户体验考虑的最后手段)
验证数据
验证是指强制接收的数据与预期相符。例如,如果我要注册一个新用户,我需要确保他们提供的电子邮件确实是你的邮箱地址。
通常有两个地方可以验证数据:客户端和服务器端。
客户端验证
前端的验证通常使用HTML 属性或 JavaScript 完成。
例如,如果我们想要一个必须填写为电子邮件的输入,并且具有最大长度,我们可以像这样实现:
<form action="example.com" method="POST">
<label for="email">Email
<input id="email" name="email" type="email" required maxlength="20">
<button type="submit">Submit</button>
</form>
如果用户尝试提交不满足我们要求的表单,浏览器将阻止该操作并向用户显示错误消息。
如果我们不想显示内置的验证 UI,可以novalidate
向表单添加该属性。这将阻止默认的验证逻辑,我们可以用自己的验证逻辑替换它。
一种方法是使用表单的方法检查表单中是否有任何无效的输入。如果表单无效,我们可以循环遍历每个输入,并使用ValidityState APIcheckValidity
来查看到底违反了哪条规则:
const form = document.querySelector('form');
form.addEventListener('submit', (event) => {
const isValid = form.checkValidity()
if (!isValid) {
const inputs = form.querySelectorAll('input')
for (const input of inputs) {
// Do some validation logic with the input
console.log(input.validity)
}
}
})
ValidityState
非常方便,因为它会给我们一个对象,其中每个键/值对代表一个验证属性及其有效性状态:
{
badInput: Boolean
customError: Boolean
patternMismatch: Boolean
rangeOverflow: Boolean
rangeUnderflow: Boolean
stepMismatch: Boolean
tooLong: Boolean
tooShort: Boolean
typeMismatch: Boolean
valid: Boolean
valueMissing: Boolean
}
这对我们很有帮助。我们可以为每个无效属性显示特定的错误消息,或者修改输入框的类名(事实上,Vuetensils就是这样进行验证的)。
我无法对您的实现做出假设,因此您必须自行决定。如果您需要更强大的功能,可以使用NPM 上众多 JavaScript 验证库之一。
无论是您自己的实现还是第三方库,客户端都存在一个主要缺陷。
任何技术用户都可以修改 HTML 验证属性,或者通过在表单外部发出 HTTP 请求来完全绕过客户端验证。这就是为什么永远不要信任来自客户端的数据如此重要。让我再说一遍。
永远不要相信来自客户的数据!
客户端验证应该通过提供即时反馈来提升用户体验。它不应该成为应用程序安全的唯一防线。
服务器端验证
由于我们不能信任来自客户端的数据,因此我们应该始终在服务器端验证数据。对于简单的应用程序,您可以创建自己的验证逻辑,但对于严肃的项目,我建议使用专用库。库的优势在于:
- 验证是一个已经解决的问题。无需重新发明轮子。
- 库通常比自定义实现效果更好,因为它们已经在更多项目中进行了测试。
- 库可以满足未来的验证需求。它们可以提供我们现在不需要但将来可能需要的功能。
- 对于服务端项目,我们不需要担心 bundle 的大小。添加更多依赖项的影响不像在客户端那么大。
我目前首选的验证库是Yup。我太喜欢它了!
如何进行服务器端验证取决于您自己。无论如何,有一些与前端相关的重要注意事项。当您在服务器上遇到验证错误时,请考虑以下几点:
- 使用适当的HTTP 状态代码进行响应(
400
大多数情况下)。 - 提供一些关于无效内容的明确信息。
- 如果有很多内容需要验证(例如 JSON 对象),请验证整个包。不要因为第一个无效值就立即抛出错误。应对所有验证问题进行响应,以避免重复请求。
- 提供唯一的错误代码(即
{ error: INVALID_EMAIL }
)可以帮助前端创建自己的错误消息词典。
净化/转义数据
与验证类似,数据清理(也称为转义)也是一种在服务器上执行的操作。数据清理是指转换或删除危险数据。它与验证不同,因为您不会拒绝输入。您可以对其进行修改,以确保其安全使用。
例如,假设您有一个表单,要求输入名字和姓氏。用户可能会输入以下内容:
名字:l33t; DROP TABLE user
姓氏:<script>alert('h4x0r')</script>
这个人很可能是个骗子,不值得信任。而且,他们的数据可能会让你遭受两种攻击:SQL 注入和跨站脚本 (XSS)。
如果您尝试将用户的名字原封不动地添加到数据库中,可能会删除整个user
表。这就是 SQL 注入。如果您将姓氏原封不动地保存,数据库就不会有问题,但如果您将该姓氏添加到 HTML 中,它可能会将任意 JavaScript 注入到页面上。示例中的 JavaScript 代码是无害的,但如果它泄露了用户机密信息呢?哎呀,XSS 攻击。
本系列主要关注 HTML 表单,因此我们不会深入探讨 XSS 或 SQL 注入。为了更深入地进行预防,我推荐OWASP 的XSS和SQL注入速查表系列。
我想强调的是,我们可以通过清理数据来避免上述两种情况。我的建议是,再次强调,要依赖那些专门用于与数据库交互的库。
对于 SQL 数据库,我建议使用对象关系映射 (ORM)库,而不是编写原始 SQL。其中许多库可以自动清理数据。对于 JavaScript 项目,我非常喜欢Knex.js和Objection.js。
每当您将用户生成的内容添加到 HTML 中时,都必须对字符串进行清理,以避免 XSS 攻击。我之前使用过一个库,叫做XSS。您可以在几个不同的地方对内容进行清理:
- 在将其保存到数据库之前。
- 从数据库中读取它之后。
- 在将其写入 HTML 文档之前。
最安全的清理内容方式是在将其添加到 HTML 文档之前。然而,我喜欢遵循零信任模式,只假设最坏的情况。在这种情况下,最好三者都做。说我偏执也好。
值得一提的是,使用前端框架创建 HTML 文档也会有所帮助。许多框架(例如Vue.js和React)会在将内容添加到页面之前自动进行转义,除非你明确告知它们不要这样做。
正确处理 JWT
JSON Web Tokens (JWT)是一项非常酷的技术,它的创建是为了解决将数据发送到多个服务的现代挑战,同时避免需要集中式服务来检查数据的有效性。
换句话说,我们可以将用户的身份验证详细信息存储在 JWT 中,并且可以确保该令牌的内容无法更改。然后,我们可以将此令牌发送给 API,而该 API 无需查询任何中央数据库即可知道是哪个用户发出的请求。API 只需打开 JWT 即可查看用户的身份验证详细信息。这太棒了。
身份验证是 JWT 目前的主要使用方式之一。然而,JWT 也存在一些明显的缺点:
- JWT 的内容对于任何能够访问它的人来说都是可见的。
- JWT 可以有一个到期日期,但不能通过编程使其失效。
出于这两个原因,我们在使用 JWT 时应该格外谨慎。遗憾的是,我看到的大多数教程都指导开发人员创建具有较长有效期的身份验证令牌 (JWT),并将保存令牌存储在 中localStorage
。我对此感到困惑。
localStorage
在客户端(例如sessionStorage
、 、 等)的 JWT 中存储敏感数据的问题IndexedDB
在于,页面上的任何 JavaScript 代码都可以访问这些数据。这些数据可能是跨站脚本,也可能是任何非我们自己编写的脚本:库和框架、来自公共 CDN 的资源、第三方代码片段,甚至是浏览器扩展。
我的另一个问题与令牌过期有关。如果具有“ADMIN”角色的用户登录我们的应用程序,他们会收到一个表明他们是“ADMIN”的授权令牌。因此,他们可以执行与“ADMIN”相关的操作(例如创建或删除其他用户),直到令牌丢失或过期。如果我们的授权令牌的过期时间为一周后,那么理论上我们所做的任何更改都可能需要一周时间才能最终生效。如果“ADMIN”角色是人为错误,而我们实际上是想将“GUEST”角色分配给该用户,该怎么办?你看到问题了吗?
这让我想到了 JWT 处理的基本规则:
- 任何包含敏感/私人/身份验证数据(用户 ID、个人身份信息等)的 JWT 都应该只存储在内存中。
- 每个 JWT 都应该有一个有效期。任何用于身份验证或授权('auth') 的 JWT 都应该有一个非常短的有效期(例如 15 分钟、24 小时等)。
这些规则解决了我们的安全问题,但也带来了一些用户体验方面的挑战。由于身份验证令牌仅存储在内存中,用户每次加载应用程序时都必须登录。如果我们的身份验证令牌使用 15 分钟的有效期,那么用户实际上每 15 分钟就会被“注销”一次。
解决这些问题的最佳方法,可以参阅Vladimir Novick撰写的优秀文章《前端客户端处理 JWT 的终极指南(GraphQL) 》 。文章内容略显复杂,但我尽量通过示例来简化:
- 创建两个身份验证路由。一个用于登录应用程序 (
/login
),另一个用于生成新的身份验证令牌 (/refresh
)。 - 当用户登录时,将返回包含授权请求所需数据的授权令牌(例如
{userId: 5, role: 'admin'}
)。该令牌的有效期较短(例如 15 分钟)。 - 登录响应还会返回一个刷新令牌。此令牌仅包含重新创建新身份验证令牌(例如
{userId: 5}
)所需的信息。它可以设置更长的有效期,以匹配您希望用户保持“登录”状态的时间。例如,一周。 - 用户通过将其凭证发送到登录路由来登录,作为回报,他们会获得一个身份验证令牌和一个刷新令牌。
- 身份验证令牌保存在内存中,并且可以放入刷新令牌
localStorage
(如果有人知道我的用户 ID,这通常并不重要)。 - 登录后,我们还设置了 14 分钟的间隔(小于授权令牌的有效期)。在此间隔内,我们将刷新令牌发送到
/refresh
路由,并将其交换为新的授权令牌。 - 这个新的身份验证令牌可以取代旧的,并且用户仍然保持“登录”状态。
- 最后一步是确保
localStorage
每次应用启动时都检查是否存在刷新令牌。如果有,我们会/refresh
在应用加载之前触发路由。这样,我们就能让用户在多个会话中保持“登录”状态。
这个 JWT 登录流程相当复杂,但我希望我已经讲清楚了。要完整地描述它,需要一篇专门的文章,所以我建议您阅读我上面提到的那篇文章。它非常棒。
防范 CSRF 攻击
跨站请求伪造 (CSRF)攻击理解起来有点复杂,但它们的工作原理是诱骗用户以攻击者的名义发出请求。一个理论上的例子或许最能解释这一点。
假设你的银行有一个表单,用于从你的账户向另一个用户的账户转账。该表单通过POST
向某个端点发送请求来实现转账,例如,yourbank.com/send-money
请求中包含两个数据值:
to
:收款用户IDamount
:您想要发送的金额(显然)。
出于安全原因,这仅在您登录后才有效(这显然是无效的)。服务器可以通过HTTP cookies验证请求。
在这个假设场景中,此表单可能容易受到 CSRF 攻击。如果攻击者足够了解银行后端的运作方式,他们就可以创建一个伪装成“承诺小猫”按钮的表单。
<form action="http://example.com/send-money" method="POST">
<input type="hidden" name="to" value="123456"/>
<input type="hidden" name="amount" value="100"/>
<button type="submit"/>Click for Kittens!!!</button>
</form>
注意上面的表单是如何利用几个hidden
输入框的,这些输入框的值设置了to
数据amount
。对于不知情的用户来说,这个表单在视觉上会呈现为一个承诺送小猫的按钮(我知道这很邪恶)。
如果您点击此按钮,它会将表单提交到您银行的/send-money
终端。如果您已登录并在浏览器中拥有有效的 Cookie,则该 Cookie 将随表单提交一起发送。这足以诱骗用户向他人汇款。
值得注意的是,这种攻击可能以多种方式发生。它可能存在于随机网站上、电子邮件中、浏览器扩展程序中等等。如果启用了 JavaScript(很可能启用了 JavaScript),它甚至可能在没有任何用户交互的情况下发生。那么,我们该如何防范呢?
CSRF 令牌
防止这种情况发生的一种方法是使用“CSRF令牌”。这些是在服务器上生成的唯一值,只有服务器知道。它们被提供给表单,以便用作隐藏输入的值,如下所示:
输入包含 CSRF 令牌后,即可提交表单,后端可以检查令牌的有效性。任何包含有效令牌的表单都可以继续处理请求。任何提交的令牌无效或缺失的表单都会被拒绝。
如果黑客想要创建与上述相同的表单,他们将无法生成自己的 CSRF 令牌(假设您确实有办法验证令牌)。
这里最棘手的部分是如何获取 CSRF 令牌,并且确保其他人无法获取。如果您在同一台服务器上创建表单,那么生成令牌并将其放入 HTML 中就很容易了。如果您使用的是 API,则需要一个提供有效 CSRF 令牌的路由。您应该将此路由配置为仅允许来自已知域的流量。这样,您就可以从有效域请求令牌,而黑客却无法访问。
验证请求来源
防止 CSRF 攻击的一个基本但巧妙的方法是检查请求的Origin
头部信息Referer
。这些headers
包含了发出请求的 URL。
这些标头最棒的地方在于它们是由浏览器设置的,无法通过编程方式修改。所以没什么好说的。如何访问这些标头取决于你使用的技术。例如,如果我使用Express,我可以创建一个类似这样的中间件:
app.use((request, response, next) => {
const allowedHosts = new Set([request.headers.host]);
let referer = request.headers.host;
let origin = null;
if (request.headers.referer) {
referer = new URL(request.headers.referer).host;
}
if (request.headers.origin) {
origin = new URL(request.headers.origin).host;
}
if (!allowedHosts.has((origin || referer))) {
return next(new Error('Unallowed origin'));
}
next();
});
- 创建所有允许主机的列表(在我们的例子中,只有相同的应用程序域有效)
- 检查是否存在
referer
and/or标头。如果存在,则获取其 URLorigin
- 如果 和 URL 都不
origin
在referer
我们允许的主机列表中,我们将拒绝该请求。
这段代码很适合作为示例,但出于生产目的,你可能需要更强大的功能。无论如何,它只需几行代码就能实现,这一点我一直很欣赏。
有关 CSRF 攻击的更多详细信息,OWASP 有一篇非常优秀的文章,其中有更详细的描述。他们的速查表系列中也有一篇文章,更详细地介绍了如何预防 CSRF 攻击。事实上,OWASP 是安全相关领域的绝佳资源,我强烈建议您花些时间阅读一下其中的内容。
对于我的 JavaScript 开发人员同行来说,Auth0有一篇专门针对 Node.js 开发和 CSRF 预防的好文章。
安全 Cookie
如上所述,CSRF 攻击会使用 Cookie 作为攻击媒介的一部分。因此,防范基于 Cookie 的攻击的一个有效方法是确保 Cookie 的安全。
对于不熟悉的人来说,Cookie 是一个HTTP 标头。更具体地说,Cookie 是通过Set-Cookie
标头分配的,如下所示:Set-Cookie: <name>=<value>; <attributes>
。
示例如下:
设置 Cookie:sessionId=38afes7a8;Domain=example.com;Max-Age=2592000;安全;HttpOnly;SameSite=strict;
与安全相关的一些属性包括:
Expires
和Max-Age
:允许您设置 cookie 有效期的时间限制。Secure
:确保仅当请求通过安全 (HTTPS) 连接发出时才会发送 Cookie。有助于防止中间人攻击。HttpOnly
:阻止 JavaScript 访问 Cookie。有助于防止 XSS 攻击。SameSite
:可设置为仅当请求来源与目标域匹配时才发送 Cookie。有助于防止 CSRF 攻击。
这些都是我认为与安全相关的属性。但正如您所见,只有SameSite
cookie 属性与 CSRF 攻击相关。这是 Web 平台相对较新的特性,对安全来说是个好消息。然而,由于它比较新,因此在旧版浏览器上不会有效。
如果您想了解有关使用 cookie 的更多信息,我推荐MDN 文档。
结束语
我意识到这篇文章中的一些内容与表单编写只是间接相关。这里的一些建议与表单根本没有直接关系。但是,我希望您同意这些信息是相关的。在为网络编写表单时,我们必须牢记这些要点。即使我们不是实施这些更改的人,我们也应该从整体上考虑我们的软件,以确保我们自己和用户的安全。
这篇文章的调研和创作耗时约 20 小时。想要告诉我你喜欢它,最好的方式就是分享。如果你想第一时间知道新文章发布,也可以订阅我的新闻邮件或在 Twitter 上关注我。
如果你错过了其他文章,不妨读一读。相信你也会喜欢的。
-第 5 部分:安全性
本文最初发表于austingil.com。
鏂囩珷鏉ユ簮锛�https://dev.to/austingil/how-to-build-html-forms-right-security-3701