lizheming

请停止使用 JWT 认证

原文链接: cryto.net

最新我看到越来越多的人推荐在 Web 应用中使用 JWT(JSON Web Tokens) 来管理用户的会话记录。然而我想说这真的是个馊主意。在这篇文章中我会解释为什么我会这么说。

为了避免造成一些困扰,我先解释一些文章中出现的名词:

  • 无状态 JWT:JWT 密钥中直接加密包含了 session 数据。

  • 有状态 JWT:JWT 密钥中仅包含了 session 记录的 ID。session 数据存储在服务端。

  • Session 密钥/cookie:一个标准的 session ID,可在网站中长时间使用。session 数据同样也是存储在服务端。

首先需要明确的是:这篇文章不是为了说服你永远不用 JWT ,只是它不适合作为一种 session 机制,并且这么使用它会很危险。在其它领域已经有类似的案例了。在文末我会简单介绍下这些案例。

前期报告

很多人错误地尝试比较 cookies 和 JWT。这个比较根本没有任何意义,就像我们将苹果与橘子进行比较一样。Cookie是一种存储机制,而JWT加密签名生成密钥的算法。

它们不是对立的,而是可以一起使用或独立使用。正确的比较方式是 “sessions vs. JWT” 以及 “cookies vs. Local Storage”。

在本文中,我将会对 session 和 JWT 令牌进行比较,稍带会说一下 “cookie 与 Local Storage”。

JWT 已知的优点

当人们推荐JWT时,他们通常会列举 JWT 的以下一个或多个优点:

  • 易于(横向)扩展

  • 使用更简单

  • 更灵活

  • 更安全

  • 内置过期功能

  • 无需向用户要求“是否同意使用 cookie”

  • 防止CSRF

  • 在移动设备上效果更好

  • 适用于禁止Cookie的用户

我将分别展开说说这些理由,为什么它们是错误或误导人的。下面的一些解释可能有点模糊,这主要是因为这些理由本身就很模糊。欢迎大家给我提交其它理由,我会高兴地更新这个列表来列举更多的理由,你可以在文章底部找到我的联系方式。

易于(横向)扩展

这是列表中唯一的技术上是对的理由,但也仅仅当你使用的是无状态JWT令牌时。然而,现实情况是,几乎没有人实际需要这种可扩展性。有很多更容易的扩展方式,除非你网站数据量和 Reddit 差不多,否则你可能不需要“无状态 session”。

列举一些扩展状态化 session 的例子:

  1. 如果服务器上运行多个后端进程:可以(在该服务器上)使用 Redis 服务进行会话存储。

  2. 如果在多台服务器上运行:可以在某台特定的服务器上运行 Redis 存储 session 数据。

  3. 如果你在多个服务器,每个服务器上有多个进程运行:使用粘性会话策略。

这些都是现有软件良好支持的场景。您的应用程序基本不需要其他操作进行扩展。

也许您认为您应该“应对未来”的应用程序,以防您超出这个范围。然而,实际上,稍后更换会话机制是相当微不足道的,当您进行转换时,唯一的成本是注销每个用户一次。这是不值得的,以实现JWT的前期,特别是考虑到我会后来的缺点。

使用更简单

事实并不是这样的。您将不得不在客户端和服务器端自己处理会话管理,而标准会话 cookie 就可以开箱即用。JWT在任何方面都不容易。

更灵活

我还没有看到有人真的解释了JWT如何更灵活。几乎每个 session 的实现都可以让您存储会话的任意数据,这与JWT的工作方式没有什么不同。据我所知,这只是一个流行程度的区别。如果您不同意,请随时与我联系举例。

更安全

很多人认为JWT令牌是“更安全”的,因为它们使用加密技术。虽然签名的Cookie比未签名的Cookie更安全,但这对JWT来说并不是唯一的,良好的会话实现也使用签名的Cookie。

“它使用加密”也不会使某些东西更安全;它必须具有特定的目的,并成为有针对性的有效解决方案。事实上,不正确使用的加密技术可以使安全性降低。

我听到很多的“更安全”论证的另一个解释是,“他们不是作为一个cookie发送”。这绝对没有意义: 一个cookie只是一个HTTP头,对于使用cookies没什么不安全的。事实上,Cookie反而受到很好的保护。例如,稍后会介绍的恶意的客户端代码。

如果您担心有人拦截您的会话cookie,则应该使用TLS,如果不使用TLS(包括JWT),任何类型的会话实现都将被拦截。

内置过期功能

我觉得这不是一个值得炫耀的特性。过期功能可以在服务器端实现,许多实现也是如此。事实上,服务器端过期是可取的,事实上它允许您的应用程序清理不再需要的会话数据,如果您使用有状态的JWT令牌并依赖其过期机制,则无法执行此操作。

无需向用户要求“是否同意使用 cookie”

这个就更大错特错了。事实上不是所有的依赖 cookie 的认证服务需要使用 cookie 的。你可以想到的任何会话机制都可以替代它。

简而言之:

  • 如果您正在使用会话或令牌实现某些功能(例如保持用户登录),则不需要请求用户同意,无论您如何存储该会话。

  • 如果您正在为其他目的使用会话或令牌(例如分析或跟踪),则无论您如何存储该会话,您都需要请求用户同意。

防止CSRF

也不尽然。大约有两种存储JWT的方法:

  • 在cookie中:现在您仍然容易受到CSRF攻击,并且仍然需要保护。

  • 其他地方,例如 LocalStorage :现在您不容易遭受CSRF攻击,但您的应用程序或站点现在需要JavaScript才能正常工作,而您已经使自己容易受到完全不同的潜在漏洞的攻击。

唯一正确解决 CSRF 的办法是CSRF令牌。会话机制与此无关。

在移动设备上效果更好

这是废话。每个移动浏览器都还支持cookies,从而支持会话。每个主要移动开发框架以及任何HTTP库也是如此。这根本不是问题。

适用于禁止Cookie的用户

不太可能。用户不只是阻止cookies,它们通常会阻止所有的持久性。这包括 LocalStorage,以及允许您持久化会话(使用或不使用JWT)的任何其他存储机制。无论您使用JWT在这里并不重要,这是一个完全独立的问题。试图让身份验证在没有Cookie的情况下工作是一个失败的原因。

除此之外,阻止所有Cookie的用户通常都会明白,这将破坏他们的身份验证功能,并单独解除对他们关心的网站的Cookie。您作为网络​​开发人员需要解决这不是一个问题,一个更好的解决方案是向您的用户解释为什么您的网站需要Cookie工作。

缺点

现在我已经涵盖了所有常见的声明,为什么他们错了,你可能认为“哦,这不算什么大事,即使它对我没有帮助我也会继续使用JWT”,然后你就大错特错了。使用JWT作为会话机制有很多缺点,其中有几个是严重的安全问题。

它们占用更多的空间

JWT令牌不小。特别是当使用无状态JWT令牌时,将所有数据直接编码到令牌中,您将很快超过cookie或URL的大小限制。您可能会决定将它们存储在 LocalStorage 中,但是...

它们不太安全

将JWT存储在cookie中时,与任何其他会话标识符没有任何区别。但是当您将JWT存储在其他位置时,您现在容易受到本文中描述的新类型的攻击(特别是“存储会话”部分)的攻击:

回到刚才我们说的:LocalStorage,这是一个非常棒的HTML5特性,它为浏览器和Cookie添加了一个键/值存储空间。那么我们应该在本地存储中存储JWT?鉴于这些令牌可以达到的大小,这可能是有道理的。Cookie 一般最大会占用 4k 的存储量。对于大尺寸的令牌,cookie可能不在此问题,本地存储将是明显的解决方案。但是,LocalStorage 不提供任何与Cookie相同的安全机制。

LocalStorage 与Cookie不同,每个请求都不会发送数据存储的内容。从本地存储中检索数据的唯一方法是使用JavaScript,这意味着任何通过内容安全策略的提供JavaScript的攻击者都可以访问和渗透。不仅如此,JavaScript也不关心或跟踪数据是否通过HTTPS发送。就JavaScript而言,令牌也只是数据,就和浏览器就像其他数据一样运行。

在这些工程师遇到麻烦之后,确保没有人会用我们的Cookie,这里我们试图忽略他们给我们的所有花哨的技巧。这似乎有点落后于我。

简单地说,使用cookies不是可选的,不管你是否使用JWT。

你不能使单个JWT令牌无效

而且还有更多的安全问题。与会话不同 - 服务器无论何时感觉到无效 - 个别的无状态JWT令牌都不能被无效。创建它们之后,它们将有效,直到它们过期,不管发生什么。这意味着您无法在检测到妥协之后使攻击者的会话无效。当用户更改密码时,也不能使旧会话无效。

你实际上是无能为力的,不能在没有建立复杂(和有状态的)的情况下“杀死”一个会话)基础设施来明确检测和拒绝它们,击败使用无状态JWT令牌开始的整个过程。

资料陈旧

与这个问题有关,还有一个潜在的安全问题。像缓存一样,无状态令牌中的数据将最终“过时”,不再反映数据库中最新版本的数据。

这可能意味着令牌包含一些过时的信息,如旧的网站URL,有人在他们的个人资料中更改 - 但更重要的是,它也可能意味着有一个具有管理员角色的令牌,即使您刚刚撤销其管理角色。因为你也不能使令牌无效,所以没有办法删除他们的管理员访问权限,而不是关闭整个系统。

执行情况较少受到测试或不存在

你可能会认为所有这些问题都只是无状态的JWT令牌,而你们大都是对的。然而,使用有状态令牌基本上等同于常规会话cookie ...

现有的会话实现(例如Express的express-session)已经在生产中运行了很多年,因此它们的安全性得到了很大的改善。当使用JWT令牌作为临时会话cookie时,您不会得到这些好处 - 您将必须滚动自己的实现(并且最有可能在此过程中引入漏洞),或使用没有看到太多真实的第三方实现世界使用。

结论

无状态JWT令牌不能被无效或更新,而且你存储的方式不同也会引入一些安全问题。有状态的JWT令牌在功能上与会话cookie相同,但没有进行经过测试和经过审查的实施或客户端支持。

除非您使用Reddit规模的应用程序,否则没有任何理由将JWT令牌用作会话机制。只需使用会话。

那么JWT有什么好的呢?

在本文的开头,我说JWT有很好的用法,但是它们不适合作为会话机制。这仍然是真的JWT特别有效的用途通常是用作单次使用授权令牌的用途。

JSON Web Token规范

JSON Web Token(JWT)是一种紧凑的,URL安全的方式来表示在双方之间转让的声明。[...]使声明能够通过消息认证码(MAC)进行数字签名或完整性保护和/或加密。

在这种情况下,“优点”是它可以像“命令”一样一次使用,一次性授权,或基本上任何其他可以说如下的情况:

你好,服务器B,服务器A告诉我,我可以,这里是(加密)证明。

例如,您可以运行文件托管服务,用户必须通过身份验证才能下载其文件,但文件本身由单独的无状态“下载服务器”提供。在这种情况下,您可能希望让应用程序服务器(服务器A)发出一次性的“下载令牌”,客户端然后可以使用它从下载服务器(服务器B)下载文件。

当以这种方式使用JWT时,有一些特定的属性:

  • 令牌是短时间的。他们只需要有效的几分钟,让客户端启动下载。

  • 令牌只能预期使用一次。应用程序服务器将为每次下载发出一个新的令牌,因此任何一个令牌仅用于请求一次文件,然后丢弃。根本没有持续的状态。

  • 应用程序服务器仍然使用会话。这只是使用令牌授权单个下载的下载服务器,因为它不需要持久状态。

正如你可以看到的,组合会话和JWT令牌是完全合理的 - 它们都有自己的目的,有时候需要两者。只需不要使用JWT持久的长寿命数据。