如何安全地存储 JWT 令牌。

2025-05-24

如何安全地存储 JWT 令牌。

本文最初发表于此处

近年来,JWT 令牌被广泛用于 Web 应用程序的身份验证和授权方法。它们允许后端开发人员对用户进行身份验证,而无需向数据库服务器或任何其他类型的存储发出任何查询。它们非常易于使用,并且使用目前互联网上最常用的数据格式 JSON。

由于这些事实,用户以错误的方式存储 JWT 令牌的情况越来越多,这使得 Web 应用程序容易受到不同类型的攻击。

但首先,什么是 JWT 令牌?

JWT 是 JSON Web Token 的缩写。JWT 实际上是 JSON 对象的加密签名,采用 base64 编码。通过对令牌进行签名,我们可以确保其内容未被任何篡改。这是通过使用最初签名时使用的密钥来验证收到的令牌来实现的。如果我们生成的签名与令牌中的签名不匹配,则应认定该令牌无效。

JWT 令牌由三部分组成,均以 base64 字符串表示:

  • 标头通常包含令牌的到期日期、用于签名的算法和额外的元数据。

  • JSON 有效负载。

  • 通过对标头和有效负载进行签名而创建的签名

标头和有效负载在签名之前以 JSON 格式存储。最终的令牌是上述 base64 数据的串联,以句点分隔。因此,JWT 令牌如下所示:

[标题].[有效负载].[签名]

现在,让我们探索一下存储 JWT 令牌的最佳方式。

我应该将 JWT 存储在本地存储中吗?

大多数人倾向于将 JWT 存储在 Web 浏览器的本地存储中。这种策略会使您的应用程序容易受到名为 XSS 的攻击。我们将仅在 JWT 上下文中讨论 XSS,您可以在此处找到更多信息。在这种攻击中,攻击者利用了本地存储可被 Web 应用程序托管的同一域中运行的任何 JavaScript 代码访问这一事实。因此,例如,如果攻击者能够找到一种方法将恶意 JavaScript 代码注入您的应用程序(通过将代码注入您使用的、而您不知情的 Node 模块中),您的 JWT 令牌就会立即被他们获取。

所以这个问题的答案是:不,永远不要将 JWT 存储在本地存储中。

但是会话存储怎么样?

嗯,让我们看看这种情况会发生什么。与本地存储类似,会话存储可以被任何在 Web 应用所在域中运行的 JavaScript 代码访问。因此,唯一不同的是,当用户关闭浏览器时,JWT 将会消失,用户下次访问 Web 应用时必须重新登录。

因此,答案是一样的:永远不要将 JWT 存储在会话存储中。

好的,但我总是可以使用浏览器的内存,对吗?

当然,如果你只通过代码访问令牌,那是一个安全的解决方案。唯一的问题是,当用户刷新浏览器时,他们必须重新登录。这不太酷吧?

我建议在用户由于某些未知原因必须在刷新浏览器时重新登录时使用此方法,

但如果这些我都用不了,那怎么办?Cookies?Cookies 都 2010 年了……

别再贬低 Cookie 了,Cookie 很棒。我们已经用了好几年了,每次浏览器请求(包括 AJAX 请求)都会自动发送 Cookie,而且超级安全!!!

遥远的声音:但是 cookie 也可以通过 javascript 访问。

确实如此,但前提是服务器未设置 HttpOnly 标志,而身份验证或授权 Cookie 中应该始终设置该标志。那么,遥远的声音,我们可以接受吗?

远处的声音:当然不是,如果有人通过不安全的 HTTP 请求发送它们怎么办?

首先,现在应该没人会用纯 HTTP 了,但即使这样,我们也可以在创建 Cookie 时设置 Secure 标志,这样它就不会通过非安全连接发送。我想我们可以继续了……

远处传来声音:“兄弟,你着急吗?你忘了 CSRF 攻击了。”

操!是啊,我忘了 CSRF 攻击了。那么,什么是 CSRF 攻击呢

CSRF 攻击是指攻击者利用浏览器的默认行为,即使在跨域请求时也发送所有 Cookie。如果处理不当,可能会导致严重的安全漏洞。此类攻击的示例如下:

  • 攻击者发送一封包含精美优惠的电子邮件,并在末尾添加一个 CTA 按钮“获得 50% 的折扣”。

  • 用户对这个超棒的优惠感到非常兴奋并点击了按钮。

  • 实际上,该按钮会向您的 Web 应用程序提交一个 POST 表单,更具体地说,是向使用新密码更改用户密码的端点提交一个 POST 表单。

  • 因为每个请求(甚至是跨域请求)都会发送 cookie,所以如果用户在先前的步骤中已登录到您的 Web 应用程序,则端点将按预期工作。

  • 现在用户已注销并且无法再登录您的 Web 应用程序。

需要指出的是,此次攻击很可能是由一个只想小试牛刀、测试一下自己实力的攻击者发起的。攻击者可能会执行更严重的操作,例如强迫用户升级到非常昂贵的套餐,甚至将用户的银行账户中的资金转入攻击者的账户。

那么我能做些什么呢?

第一种选择就是忘掉这件事,永远不要告诉任何人,并祈祷没有人发现(尤其是恶意黑客)。这样你就可以开始了!开玩笑的……

您可以在向服务器发出的每个请求(通常是任何 POST、PUT 或 DELETE 请求)中使用后端生成的令牌,这样当用户执行请求时,您可以通过从缓存甚至直接从数据库获取令牌来检查令牌是否有效。但这要求您在每次用户重定向到新页面时都创建一个新的令牌?那么,还有其他选择吗?

不幸的是,多年来,唯一的安全方法是在使用基于 Cookie 的身份验证时使用 CSRF 令牌。从 2016 年开始,现代浏览器开始实施名为 SameSite 的 Cookie 策略。SameSite 可以采用以下三个值之一:

它们各自在各自的场合下都是有用的。

第一个选项 None 允许在任何可能的请求中发送 Cookie,包括跨域请求。多年来,浏览器一直以这种方式处理 Cookie。大多数现代浏览器的 SameSite 的新默认值是 Lax,尽管并非所有浏览器都使用 Lax 作为默认的 SameSite Cookie 策略。

您可以在此处找到浏览器兼容性

那么什么是 Lax?如果请求动词为 GET,Lax 允许在跨域请求中发送 Cookie。所有其他请求均不会包含采用 Lax SameSite 策略的 Cookie。这样,它允许在例如将用户从电子邮件重定向到应用的仪表板屏幕时使用 Cookie,但不允许恶意表单将数据发布到敏感端点。

需要指出的是,Lax 适用于所有重定向 GET 请求。它不会在 Ajax 请求中发送 Cookie,因此没有人能够在其网站和请求中添加一些代码(例如/me端点)来窃取已登录用户的个人数据。对于 iframe,即使是重定向请求,它也会被忽略。

因此,从跨域 GET 请求发送 Lax cookie 的最常见方式如下。

  • 用户点击重定向到您网站的链接

  • 用户提交一个 GET 表单请求,该请求重定向到您的网站(如果您想提供一种使用表单从另一个网站向查询字符串传递动态参数的方法,则很有用)

  • 如果使用类似window.location.href=”yoursite…”的命令。

现在,Strict SameSite 策略将不允许 Cookie 以任何方式通过跨域请求传递。这在某些特殊情况下可能有用,但您必须了解,如果您使用此策略,即使是简单的链接重定向也会导致用户从 Web 应用程序中注销。当然,这是最安全的策略。

所以,我只要使用 Lax 或 Strict,就可以高枕无忧了,不用担心 CSRF 或 XSS 攻击。对吧?

差不多。你必须确保你的用户使用的浏览器支持这种功能。如果不支持,最好的办法是告知用户你的应用目前不支持该浏览器版本,建议他们升级到新版本。

如果您不使用这样的消息,服务器可能会尝试设置 SameSite 策略,但浏览器会忽略它。例如,Internet Explorer 不支持此类功能。如果您需要明确支持旧版浏览器,则应该回退到 CSRF 令牌实现。

如果您发现这篇博文有用,您可以订阅我的时事通讯并第一时间了解任何新文章。

背景矢量由vectorpocket创建 – www.freepik.com

文章来源:https://dev.to/gkoniaris/how-to-securely-store-jwt-tokens-51cf
PREV
NodeJS 日志记录,从初学者到专家。
NEXT
网页之旅🛣️ - 浏览器的工作原理