JSON Web 令牌不适用于重复验证同一用户:请使用会话令牌
2021年4月17日更新:别忘了看看这篇文章下的所有评论。它们都富有洞察力,甚至比这篇文章本身更有价值。感谢那些抽出时间写下真知灼见的人。我现在正在学习更多关于网络安全的知识,以便能够消化这些评论。
网络上有无数的文章比较了会话令牌和 JSON Web 令牌(JWT)作为用户身份验证方法的优劣。对于首次尝试创建全栈 Web 应用的人(比如我自己)来说,这确实令人困惑。没有人清楚地解释每种方法的优缺点。(也许没有人确切知道)。
花了几天时间研究这个主题,我得出的结论是,我将选择会话令牌,因为 JWT 不适用于重复验证同一个用户。
然而,一周后,我肯定会忘记自己为什么得出这个结论。这个话题真的很复杂。这篇文章是我写给未来自己的一份备忘录,希望也适合你。
免责声明:我不是网络安全专家。尽管我尽力确保所有内容准确无误,但以下内容可能并非完全正确。如果您认为我错了,请发表评论。我只是想知道真相。
对于初学者
我就不多说会话令牌和 JWT 是什么了。网上已经有很好的解释了。
对于会话令牌,我建议阅读Kamani (2017),他提供了我见过的最简洁的会话令牌介绍(以及为什么 cookie 对它们来说是必要的)。
对于 JWT,请查看Calandra (2019),他很好地解释了 JWT 诞生的背景。
为了公平地比较会话令牌和 JWT,我们首先需要明确有关 JWT 的一件事:
不要将 JWT 存储在 localStorage 中
我看过很多关于 JWT 的教程,演示了前端应用如何将 JWT 存储在localStorage中,但这些教程总是会引来一些评论:“不要将 JWT 存储在 localStorage 中,因为那里容易受到跨站脚本 (XSS) 攻击”。我发现Chenkie (2020a)非常清楚地解释了这些评论的真正含义,并罕见地演示了如何进行 XSS 攻击。(虽然需要注册(免费),但这个 2.5 分钟的视频教程确实值得一看。)
因此,JWT 应该存储在 Cookie 中,就像会话令牌需要存储在 Cookie 中一样。Cookie 容易受到另一种称为跨站请求伪造 (CSRF) 的攻击,但我们可以使用类似以下库来处理它(如果我理解正确的话,至少可以部分处理)csurf
(详情请参阅Chenkie 2020b )。
注意:Vladimir 和 Gopal (2019)建议将 JWT 存储在内存中,这样既不容易受到 XSS 攻击,也不容易受到 CSRF 攻击。这种方法或许值得一试。
因此,对我们来说相关的比较是“存储在 cookie 中的会话令牌”与“存储在 cookie 中的 JWT”。
会话令牌的缺点
会话令牌的主要缺点是,当其他服务器处理用户的请求(当应用程序由多台服务器提供服务时)或同一台服务器重启(例如修复错误等)时,已登录的用户将被踢出,因为用户是否已通过身份验证取决于特定服务器。有关详细信息,请参阅Calandra (2019)的示例。
这个问题可以通过使用“会话存储”库来解决connect-redis
(请参阅Express.jsexpress-session
的文档,了解所有会话存储库列表)。其思路是将会话令牌保存在一个专用数据库中,每次用户使用其会话令牌发出 HTTP 请求时,接收请求的服务器都会查询该数据库,以了解该请求是否来自已通过身份验证的用户。因此,性能会受到影响,因为从数据库检索数据总是需要一些时间。
使用会话令牌需要 Redis 或数据库。图片来源:Awad (2019)
JWT 通过使服务器“无状态”来解决这个问题:用户的身份验证状态嵌入在令牌中,因此服务器无需记住它。其他服务器或重新启动的服务器可以通过解码随每个 HTTP 请求一起发送的 JWT 来判断用户是否已通过身份验证。因此,已登录的用户不会被踢出。我们不需要任何数据库来存储用户的身份验证状态。因此,性能不会受到影响。
JWT 的缺点
然而,JWT 的缺点是,服务器一旦签发了 JWT,就无法控制令牌的过期时间。即使用户已注销,应用也无法通过服务器代码或前端代码将其终止(因为出于安全考虑,JWT 应该存储在 HTTP 专用 Cookie 中)。
令牌有效期越长,被盗的风险就越高。因此,我们想将 JWT 设置为短时间过期,比如 15 分钟。这样一来,用户体验就会大打折扣:每个用户在使用应用时,每 15 分钟就会被强制退出一次。这太糟糕了。
延长令牌的有效期(即刷新令牌)需要向用户发放“刷新令牌”,并将其存储在数据库中。令牌过期后,服务器将检查通过 HTTP 请求发送的刷新令牌,并将其与数据库中存储的令牌进行比对。JSON Web 令牌需要 Redis 或数据库来存储刷新令牌。图片:改编自Awad (2019)
这听起来很熟悉……是的,它和会话令牌一样,需要存储在数据库中,以便不同的服务器验证用户的身份验证状态。JWT 本来应该可以解决这个问题,但现在我们又回到了同样的情况:访问数据库来验证用户的身份验证状态。而且情况更加复杂:我们现在不是在检查令牌本身,而是在检查颁发的令牌以刷新原始令牌……这就是为什么Awad (2019)说他在一个简单的 Web 应用中使用会话令牌而不是 JWT。
另一方面,会话令牌可以在服务器代码中销毁,例如在用户注销时。延长会话有效期很简单,只需将选项设置rolling
为即可true
。express-session
总结
因此,使用 JWT 的主要优势(即通过放弃会话令牌数据库来提高性能)被完全抵消了,因为它需要刷新令牌数据库以避免不时强制用户注销。确切地说,并非完全抵消。Awad (2019)指出,使用 JWT 可以最大限度地降低数据库访问频率:服务器仅在 JWT 过期时才需要查询刷新令牌数据库,而每次用户发送 HTTP 请求时都需要访问会话令牌数据库。但是,随着 JWT 生命周期的缩短,为了增强安全性,这种差异会越来越小。
理解了这些之后,我终于明白了Slootweg (2016)所说的 JWT 并非用于管理用户会话的含义。JWT 的设计初衷就不适用于重复验证用户身份。一旦开始处理用户会话,JWT 的优势就会大打折扣。
以上就是我对会话令牌和 JWT 之间区别的理解。如果我遗漏了什么,请告诉我。我渴望了解更多。
在我们离开之前……
本文发表于一本名为《京都 Web Dev Survey》的刊物。我们的想法是在日本古都京都市(我现在居住的地方)举办一场关于 Web 开发的线上会议。每篇文章(相当于一次研讨会)结束后,我们都会带你游览京都市。
可惜的是,今天著名的樱花大多已经凋谢了。不过,京都市的许多地方,棣棠花却盛开着鲜艳的黄色:
大觉寺(作者于2021年4月5日拍摄)
松野神社(作者于2021年4月10日拍摄)
参考
Ben Awad (2019) “为什么我没有使用 JWT 令牌进行身份验证”,YouTube,2019 年 4 月 21 日。
Mariano Calandra (2019) “为什么我们在现代网络中需要 JSON Web Token (JWT)? ”,Start It Up,2019 年 9 月 6 日。
Ryan Chenkie (2020a) “窃取 JSON Web 令牌”,React 安全基础知识,2020 年 5 月。
Ryan Chenkie (2020b) “添加跨站请求伪造令牌”,React 安全基础知识,2020 年 5 月。
Soham Kamani (2017)“ Web 安全要点——会话和 Cookie ”,sohamkamani.com,2017 年 1 月 8 日。
Sven Slootweg (2016)“停止在会话中使用 JWT ”,joepie91 的 Ramblings,2016 年 6 月 13 日。
Vladimir 和 Tanmai Gopal (2019)“在前端客户端 (GraphQL) 上处理 JWT 的终极指南”,Hasura 博客,2019 年 9 月 9 日。
文章来源:https://dev.to/masakudamatsu/json-web-tokens-are-not-meant-for-authenticating-the-same-user-repeatedly-use-session-tokens-instead-pno