请停止使用本地存储
认真的,别再这样了。
我不知道究竟是什么原因,驱使这么多开发者将会话信息存储在本地存储中,但无论原因是什么:这种做法必须停止。事情已经完全失控了。
几乎每天我都会偶然发现一个新网站将敏感的用户信息存储在本地存储中,令我感到困扰的是,如此多的开发人员这样做会让自己面临灾难性的安全问题。
让我们推心置腹地谈谈本地存储以及为什么您应该停止使用它来存储会话数据。
什么是本地存储?
如果我刚才有点暴躁,我很抱歉。你不该受这样的对待!哎呀,你可能根本就不知道本地存储是什么,更别说用它来存储你的会话信息了!
让我们从基础开始:本地存储是 HTML5 的一项新功能,它基本上允许您(Web 开发人员)使用 JavaScript 在用户浏览器中存储所需的任何信息。很简单,对吧?
实际上,本地存储只是一个大型的旧式 JavaScript 对象,你可以向其中附加数据(或从中删除数据)。以下是一段 JavaScript 代码示例,它将我的一些个人信息存储在本地存储中,然后将其回显给我,然后(可选)将其删除:
// You can store data in local storage using either syntax
localStorage.userName = "rdegges";
localStorage.setItem("favoriteColor", "black");
// Once data is in localStorage, it'll stay there forever until it is
// explicitly removed
alert(localStorage.userName + " really likes the color " + localStorage.favoriteColor + ".");
// Removing data from local storage is also pretty easy. Uncomment the lines
// below to destroy the entries
//localStorage.removeItem("userName");
//localStorage.removeItem("favoriteColor");
如果你在浏览器中的测试 HTML 页面上运行上面的 JavaScript 代码,你会在警告消息中看到“rdegges really likes the color black.”这句话。然后,如果你打开开发者工具,你将能够看到userName
和favoriteColor
变量都存储在浏览器的本地存储中:
现在你可能想知道,是否有办法使用本地存储,让你存储的数据在某个时间点自动删除,这样你就不用手动删除每个变量了。幸运的是,HTML5 工作组(大声喊出来!)为你提供支持。他们在 HTML5 中添加了一种名为 sessionStorage 的功能,其工作原理与本地存储完全相同,只是当用户关闭浏览器标签页时,它存储的所有数据都会自动删除。
本地存储有何优势?
既然我们对本地存储是什么已经有了共识,那就来聊聊它为什么这么酷吧!虽然本文的重点是劝你不要使用本地存储来存储会话数据,但本地存储仍然有一些有趣的特性。
首先:它是纯 JavaScript 的!Cookie(唯一真正意义上的本地存储替代方案)的一大弊端在于,它需要由 Web 服务器创建。真是的!Web 服务器枯燥、复杂,难以操作。
如果您正在构建静态网站(例如单页应用),使用本地存储之类的存储方式意味着您的网页可以独立于任何 Web 服务器运行。它们不需要任何后端语言或逻辑来在浏览器中存储数据:它们可以随心所欲地运行。
这是一个非常强大的概念,也是本地存储受到开发人员如此欢迎的主要原因之一。
本地存储的另一个优点是它不像 Cookie 那样有大小限制。本地存储在所有主流 Web 浏览器中至少提供 5MB 的数据存储容量,这比 Cookie 中 4KB(最大大小)的存储空间要大得多。
如果您想在浏览器中缓存一些应用程序数据以供日后使用,本地存储就显得尤为有用。由于 4KB(Cookie 的最大大小)并不大,因此本地存储是您仅有的替代方案之一。
本地存储的弊端
好的。我们谈论了好的方面,现在让我们花一两分钟(或两分钟!)来谈谈坏的方面。
本地存储太基础了。呼。说完心里话,感觉好多了。本地存储只是一个极其基础、简单的 API。
我觉得大多数开发人员都没有意识到本地存储实际上有多么基本:
-
它只能存储字符串数据。真是糟糕。这使得它对于存储比简单字符串稍微复杂一点的数据几乎毫无用处。当然,你可以将所有内容(包括数据类型)序列化到本地存储中,但这太丑陋了。
-
它是同步的。这意味着您运行的每个本地存储操作都将一次执行一次。对于复杂的应用程序来说,这是一个大忌,因为它会减慢应用程序的运行速度。
-
它不能被网络工作者使用=/ 这意味着如果你想构建一个利用后台处理来提高性能的应用程序,chrome 扩展,诸如此类的东西:你根本不能使用本地存储,因为它不适用于网络工作者。
-
它仍然限制了您可以存储的数据大小(所有主流浏览器约为 5MB)。对于构建数据密集型或需要离线运行的应用程序的人来说,这是一个相当低的限制。
-
页面上的任何 JavaScript 代码都可以访问本地存储:它没有任何数据保护。出于安全原因,这是最大的问题(也是我近年来最讨厌的事情)。
简而言之,您应该在以下唯一情况下使用本地存储:当您需要存储一些公开可用的信息时,这些信息根本不敏感,不需要在高性能应用程序中使用,不大于 5MB,并且纯由字符串数据组成。
如果您使用的应用不符合上述描述:请不要使用本地存储。请使用其他存储(稍后会详细介绍)。
为什么本地存储不安全并且不应使用它来存储敏感数据
实际情况是:本地存储的大多数缺点其实并不那么重要。你仍然可以使用它,但应用程序速度会稍微慢一些,开发者也会感到些许烦恼。但安全性就不同了。了解和理解本地存储的安全模型非常重要,因为它会以你可能没有意识到的方式极大地影响你的网站。
本地存储的问题是它不安全!一点也不安全!所有使用本地存储来存储敏感信息(例如会话数据、用户详细信息、信用卡信息(即使是临时的!)以及任何你不想在 Facebook 上公开发布的信息)的人都是错误的。
本地存储并非设计为浏览器中的安全存储机制。它被设计为一个简单的字符串键值存储,开发者可以使用它来构建稍微复杂的单页应用。仅此而已。
世界上最危险的东西是什么?没错!JavaScript。
可以这样想:当您将敏感信息存储在本地存储中时,您实际上是在使用世界上最危险的东西将最敏感的信息存储在有史以来最糟糕的保险库中:这不是最好的主意。
这个问题的本质是跨站脚本攻击(XSS)。我不会详细解释 XSS,但以下是大致的介绍:
如果攻击者能够在您的网站上运行 JavaScript,他们就能检索您存储在本地存储中的所有数据,并将其发送到他们自己的域名。这意味着您本地存储中的任何敏感信息(例如用户的会话数据)都可能被盗用。
现在,您可能会想:“那又怎样?我的网站是安全的。没有攻击者可以在我的网站上运行 JavaScript。”
这话倒是说得通。如果您的网站真正安全,没有攻击者能够在您的网站上运行 JavaScript 代码,那么从技术层面上来说,您的网站就是安全的。但实际上,做到这一点极其困难。让我来解释一下。
如果您的网站包含来自您域外来源的任何第三方 JavaScript 代码:
- 引导链接
- 链接到 jQuery
- 链接到 Vue、React、Angular 等。
- 任何广告网络代码的链接
- 链接到 Google Analytics
- 任何跟踪代码的链接
那么,您的网站目前面临着攻击者在网站上运行 JavaScript 的风险。假设您的网站嵌入了以下脚本标签:
<script src="https://awesomejslibrary.com/minified.js"></script>
在这种情况下,如果 awesomejslibrary.com 被入侵并且其minified.js
脚本被更改为:
- 循环遍历本地存储中的所有数据
- 将其发送到为收集被盗信息而构建的 API
……那你就彻底完蛋了。在这种情况下,攻击者很容易就能攻破你本地存储中的任何数据,而你却浑然不知。这可不是什么好事。
作为工程师,我认为我们经常会认为我们永远不会在自己的网站中嵌入第三方 JavaScript。但在现实世界中,这种情况很少发生。
在大多数公司,营销团队直接使用各种所见即所得的编辑器和工具来管理公共网站。你能确定你的网站上没有使用第三方 JavaScript 吗?我认为“不能”。
因此,为了谨慎起见并大大降低发生安全事件的风险:不要在本地存储中存储任何敏感信息。
PSA:不要将 JSON Web Tokens 存储在本地存储中
虽然我觉得我在上一节中已经明确表示过,你永远不应该将敏感信息存储在本地存储中,但我觉得有必要特别指出 JSON Web 令牌 (JWT)。
我目前发现的最大的安全隐患就是那些将 JWT(会话数据)存储在本地存储的人。很多人没有意识到 JWT 本质上和用户名/密码是一样的。
如果攻击者获取了您的 JWT 副本,他们就可以以您的名义向网站发出请求,而您对此毫不知情。请像对待信用卡号或密码一样对待您的 JWT:切勿将其存储在本地存储中。
成千上万的教程、YouTube 视频,甚至大学和编程训练营的编程课程,都在错误地教导新开发者将 JWT 存储在本地存储中作为身份验证机制。这些信息是错误的。如果你看到有人告诉你这样做,请立即逃离!
用什么来代替本地存储
那么,鉴于本地存储的种种缺点,您应该使用什么呢?让我们来探索一下替代方案!
敏感数据
如果需要存储敏感数据,则应始终使用服务器端会话。敏感数据包括:
- 用户 ID
- 会话 ID
- JWT
- 个人信息
- 信用卡信息
- API 密钥
- 以及任何你不想在 Facebook 上公开分享的内容
如果您需要存储敏感数据,请按以下步骤操作:
-
当用户登录您的网站时,请为其创建会话标识符,并将其存储在加密签名的 Cookie 中。如果您使用的是 Web 框架,请查阅“如何使用 Cookie 创建用户会话”并按照该指南操作。
-
确保你的 Web 框架所使用的 Cookie 库设置了
httpOnly
Cookie 标志。此标志将使浏览器无法读取任何 Cookie,而这是为了安全地使用带有 Cookie 的服务器端会话所必需的。阅读Jeff Atwood 的文章了解更多信息。他就是那个人。 -
确保您的 cookie 库也设置了
SameSite=strict
cookie 标志(以防止CSRF攻击)以及secure=true
标志(以确保只能通过加密连接设置 cookie)。 -
每次用户向您的网站发出请求时,使用他们的会话 ID(从他们发送给您的 cookie 中提取)从数据库或缓存中检索他们的帐户详细信息(取决于您的网站有多大)
-
提取并验证用户的帐户信息后,请随意提取任何相关的敏感数据
这种模式简单、直接,而且最重要的是:安全。没错,你绝对可以用这种模式来扩展大型网站。别告诉我 JWT 是“无状态”且“快速”的,你必须使用本地存储来存储它们:你错了!
非字符串数据
如果您需要在浏览器中存储非敏感数据,且这些数据并非纯字符串,那么 IndexedDB 是您的最佳选择。它是一个 API,允许您在浏览器中使用类似数据库的对象存储。
IndexedDB 的优点在于您可以使用它来存储类型信息:整数、浮点数等。您还可以定义主键、处理索引并创建事务以防止数据完整性问题。
这篇Google 教程是学习(和使用)IndexedDB 的一个很棒的教程。
离线数据
如果您需要您的应用程序离线运行,最好的选择是结合使用 IndexedDB(上文)和 Cache API(它是 Service Workers 的一部分)。
Cache API 允许您缓存应用需要加载的网络资源。
这篇Google 教程是学习(和使用)Cache API 的一个很好的教程。
请停止使用本地存储
现在我们有机会讨论本地存储,我希望您明白为什么您(可能)不应该使用它。
除非您需要存储以下公开信息:
- 一点也不敏感
- 不需要在超高性能应用程序中使用
- 不大于 5MB
- 由纯字符串数据组成
……别用本地存储!用对工具。
另外,请务必,无论如何,不要将会话信息(例如 JSON Web Tokens)存储在本地存储中。这是一个非常糟糕的主意,会让您面临各种各样的攻击,这些攻击可能会严重损害您的用户。
有问题吗?请给我发邮件。
在外面要注意安全=)
注意:对于那些读到这里可能想知道为什么我没有特别提到内容安全策略 (CSP)可以减轻 XSS 攻击影响的人来说,我特意选择不包含它,因为它在我上面描述的情况下毫无用处。即使你使用 CSP 将所有第三方 JavaScript 域名列入白名单,如果第三方提供商被攻破,这也无助于阻止 XSS 攻击。
顺便说一句:子资源完整性(虽然很酷)也不是解决这个问题的万能方案。对于大多数营销工具、广告网络等(它们是迄今为止最常用的第三方 JavaScript 类型)来说,子资源完整性几乎从未被使用过,因为这些脚本的提供者希望频繁更改它们,以便能够悄无声息地为用户更新功能。
更新:我并非唯一一个认为不应该在本地存储中存储任何敏感信息的人。OWASP 也持同样的观点:
文章来源:https://dev.to/rdegges/please-stop-using-local-storage-1i04... 换句话说,您的应用程序所需的任何身份验证都可以被具有存储数据的计算机本地权限的用户绕过。因此,建议不要将任何敏感信息存储在本地存储中。