身份验证:JWT 使用与会话
Posted
技术标签:
【中文标题】身份验证:JWT 使用与会话【英文标题】:Authentication: JWT usage vs session 【发布时间】:2020-08-29 13:57:37 【问题描述】:在身份验证等情况下,与会话相比,使用 JWT 有什么优势?
它是用作独立方法还是在会话中使用?
【问题讨论】:
【参考方案1】:与使用“会话”本身相比,JWT 没有任何好处。 JWT 提供了一种在客户端而不是在服务器上维护会话状态的方法。
人们问这个问题时通常的意思是“使用 JWT 与使用 服务器端会话相比有什么好处”。
对于服务器端会话,您要么必须将会话标识符存储在数据库中,要么将其保存在内存中并确保客户端始终访问同一个服务器。这两个都有缺点。在数据库(或其他集中式存储)的情况下,这将成为一个瓶颈和需要维护的东西 - 本质上是对每个请求进行的额外查询。
使用内存解决方案,您会限制水平扩展,并且会话会受到网络问题(客户端在 Wifi 和移动数据之间漫游、服务器重启等)的影响。
将会话移至客户端意味着您移除了对服务器端会话的依赖,但它也带来了一系列挑战。
安全地存储令牌。 安全运输。 JWT 会话有时很难失效。 相信客户的主张。这些问题由 JWT 和其他类似的客户端会话机制共享。
JWT 尤其解决了其中的最后一个问题。了解什么是 JWT 可能会有所帮助:
这是一些信息。对于用户会话,您可以包括用户名和令牌过期的时间。但它可以是任何东西,甚至是会话 ID 或用户的整个配置文件(但请不要这样做)。
它有一个安全的签名,可以防止恶意方生成假令牌(您需要访问服务器的私钥才能对其进行签名,并且您可以验证它们在签名后没有被修改)。
您将它们与每个请求一起发送,就像发送 cookie 或 Authorization
Header 一样。事实上,它们通常在 HTTP Authorization
标头中发送,但使用 cookie 也可以。
令牌已签名,因此服务器可以验证其来源。我们将假设服务器信任自己的安全签名能力(您应该使用标准库:不要尝试自己做,并正确保护服务器)。
关于安全传输令牌的问题,答案通常是通过加密通道发送,通常是 httpS。
关于将令牌安全地存储在客户端中,您需要确保坏人无法获取它。这(主要)意味着防止来自不良网站的 JS 读取令牌以将其发回给他们。使用用于缓解其他类型 XSS 攻击的相同策略可以缓解这种情况。
如果您需要使 JWT 失效,肯定有办法实现这一点。仅为请求终止“其他会话”的用户存储每个用户的 epoch 是一种非常有效的方法,可能已经足够好了。如果应用程序需要每个会话失效,那么可以以相同的方式维护会话 ID,并且“已删除令牌”表仍然可以维护为比完整的用户表小得多(您只需要保留比最长允许的令牌生命周期)。因此,使令牌无效的能力部分否定了客户端会话的好处,因为您必须保持此会话终止状态。这很可能是一个比原始会话状态表小得多的表,因此查找仍然更有效。
使用 JWT 令牌的另一个好处是,使用您可能期望拥有的所有语言中可用的库来实现相当容易。它也完全脱离了您最初的用户身份验证方案 - 如果您迁移到基于指纹的系统,则无需对会话管理方案进行任何更改。
一个更微妙的好处:因为 JWT 可以携带“信息”并且可以被客户端访问,您现在可以开始做一些聪明的事情。例如,提醒用户他们的会话将在他们注销前几天到期,让他们可以根据令牌中的到期日期重新进行身份验证。随心所欲。
简而言之:JWT 回答了其他会话技术的一些问题和缺点。
“更便宜”的身份验证,因为您可以消除数据库往返(或者至少有一个小得多的表要查询!),这反过来又实现了水平可扩展性。
防篡改客户端声明。
虽然 JWT 没有解决安全存储或传输等其他问题,但它不会引入任何新的安全问题。
JWT 存在很多负面因素,但如果您实现与其他类型身份验证相同的安全性,那就没问题了。
最后一点:它也不是 Cookie 与令牌。 Cookies 是一种用于存储和传输信息位的机制,也可用于存储和传输 JWT 令牌。
【讨论】:
值得注意的是,服务器端会话也不必在服务器上存储任何信息。服务器可以像 JWT 一样使用客户端作为存储。真正的区别在于 1)通过将值作为请求标头而不是 cookie 标头传递来避免浏览器安全规则,以及 2)具有 JWT 的标准化格式。 您认为在本地存储中存储 jwt 是否安全?如果没有,在哪里可以安全地保存它以便用户保持登录状态? 在您的评论中,您没有谈论基于缓存的解决方案 + 会话令牌。在我看来,这比 JWT + “killed tokens” 表“更好”,因为有了这个“killed tokens”表,你无论如何都需要一个 DB 访问权限,你还将拥有会话 + 缓存(也很小)。并且持久化 JWT 比持久化会话更痛苦,因为你需要使用 refresh_token (所以要维护两个令牌)......任何评论都会非常感激......特别是如果你有一些数字来展示 JWT + kill_table 比会话 + 缓存更有效 ;-) 不同之处在于会话表包含每个会话的条目。可能是数百万用户。如果一个被杀死的令牌表被杀死,它只需要一行作为一个令牌,如果该令牌过期,可以通过删除该行来进一步减少它。所以被杀死的令牌查找是在一个小得多的表中。在现实世界的示例中,对于大型用户群而言,性能差异有几个数量级。 @TheTahaan localStorage 不建议存储 JWT。您共享的链接也提到了这一点。这个blog 很好地解释了为什么 JWT 不应该存储在 localStorage 中。【参考方案2】:简短的回答是:无。
更长的版本是:
在阅读the GraphQL docs 中的此建议后,我实施了 JWT 进行会话管理:
如果您不熟悉任何这些身份验证机制,我们 推荐使用 express-jwt 因为它很简单而不牺牲 任何未来的灵活性。
实现确实很简单,因为它只是增加了一点复杂性。然而,过了一会儿,我(和你一样)开始想知道有什么好处。事实证明,就会话管理而言,JWT 很少(或可能没有),正如这篇博文详细解释的那样:
Stop using JWT for sessions
【讨论】:
如果您需要跨域工作怎么办?在这种情况下不能使用 AFAIK cookie。 在这种情况下,您将牺牲浏览器提供的 XSS 安全性。恕我直言,应尽可能避免跨站点会话。请注意abc.example.com
和 example.com
通常被浏览器视为同一个站点。【参考方案3】:
我有一个类似的问题,在 JWT 和令牌 + 缓存之间进行用户身份验证。
阅读这些文章后,我很清楚 JWT 承诺的好处并没有超过它带来的问题。所以令牌+缓存(Redis/Memcached)是我要走的路。
Auth Headers vs JWT vs Sessions — How to Choose the Right Auth Technique for APIs
Authentication Techniques for APIs
Stop using jwt for sessions
【讨论】:
【参考方案4】:我的两分钱,在路上与 joepie91 的著名博文形成对比。
考虑到今天(和明天)的应用程序(大部分)是云原生的无状态 JWT 身份验证具有经济效益, 随着应用程序的扩展而扩展:云应用程序每过一秒都会产生成本。 当用户不再需要“针对”会话存储进行身份验证时,此成本就会降低。
处理 24/7 全天候运行会话商店需要花钱。 在 K8S 的世界中,您无法摆脱基于内存的解决方案,因为 pod 是短暂的。 出于完全相同的原因,粘性会话不会很好。
存储 存储数据需要花钱。在 SSD 中存储数据的成本更高。 会话相关操作需要快速解决,所以不能选择光驱。
I/O 一些云提供商会为与光盘相关的 I/O 收费。
带宽 一些云提供商对服务器实例之间的网络活动收费。 这适用,因为几乎可以肯定 API 和会话存储是单独的实例。
会话存储集群 成本进一步增加了所有上述成本。
【讨论】:
“在 K8S 的世界中,你无法摆脱基于内存的解决方案,因为 pod 是短暂的”不知道你的意思是什么。 Redis 绝对可以在 K8S 环境中运行,而且 redis pod 经常失败以影响您的用户似乎不太可能。 @quietContest 我个人不喜欢在构建软件时处理可能性。顺便说一句,除了解决方案的稳定性之外,攻击可能会导致软件失败并重新启动 Pod——这会导致会话丢失。出于这个原因,我会选择基于 JWT 的解决方案。 “我个人不喜欢在构建软件时处理可能性”。我认为我们都更喜欢这样,这就是为什么我们不应该构建依赖于内存数据存储永远不会失败的系统,因为这种可能性似乎相当高。至于你的另一点,如果你有一个能够持续关闭你的 redis 实例的攻击者,那么解决方案可能不需要涉及使用 JWT。 @quietContest 始终如一或一生一次的事件在这方面对我来说是相同的。即,良好的 DDoS 攻击可能导致服务器“注销用户”。这不利于软件的可靠性声誉。无论如何,我认为redis对于会话管理来说太过分了。它需要成本并且需要扩展,而(安全地)将 JWT 存储在 cookie 中则不需要。 @quietContest 感谢您的意见,喜欢讨论!【参考方案5】:如果您在 AWS 上,另一个稍微不同的观点可能会很有用。
我们在 AWS ElastiCache 上实施了 php5.x 会话存储,以跨多个服务器集中会话存储。
在我们迁移到 PHP7 之前,它一直运行良好。很难为 PHP7 进行配置,并且我们一直受到间歇性问题的困扰,对于特定用户,会话似乎“失败/不匹配/有点混乱”,然后他们无法登录该设备,直到旧会话过期。
我们转而使用 DynamoDb 来存储会话,不再出现问题。它稍微慢一些,但仅在登录(会话存储)阶段才明显。
在此过程中,我们实施了 AWS cognito 来替换我们的身份验证,并开始使用 API-Gateway 通过 lambda python 函数传递内容。
我们使用 PHP SDK 向 Cognito 进行身份验证,然后将 JWT 存储在 cookie 中,但仍然使用 PHP 会话来保持我们的旧代码正常工作。
现在我们有两个堆栈,两全其美:PHP7 做到了一点,将主要内容提供给用户(非常快速)。然后 JS 接管并使用 JWT 提供额外的内容。
我认为 JWT 的优点在于它可以在这两个堆栈之间传递并用于在两种情况下对用户进行身份验证。
现在我们想知道是否值得冒险并完全切换到新的 JWT 系统?
在 PHP 中,我们仍然使用旧会话,但我们还将令牌传递给 cognito 以对其进行身份验证。这是一点额外的安全性,可能不是必需的,但它给人一种温暖舒适的感觉。再说一次,使用 dynamoDb 可以节省成本和维护。
【讨论】:
听起来不漂亮,但据说很实用。但据我所见,特别是在您的情况下,JWT 令牌的安全性低于服务器端会话(由于 XSS 附加的可能性)。因为后者可以存储在安全的 httponly samesite=lax 或严格的 cookie 中。 看来,如果你有 XSS 漏洞,SameSite won't help 针对 CSRF。因此,服务器端会话可能容易受到 CSRF 的攻击。因此,看似客户端会话的安全性并不低。 我想我遵循你所说的 @x-yuri 但我也认为我们是安全的,因为 PHP 堆栈不会从 JWT cookie 中获取任何“指令”。它只是通过会话登录并保存 JWT 以供其他堆栈使用。还是我错过了什么? 不久前,我认为 JWT 令牌(或者更确切地说是客户端会话)不如服务器端令牌安全。因为 JWT 容易受到 XSS 攻击。对于服务器端会话,如果您使用 httponly cookie,则消除了这种可能性。但是服务器端会话可能容易受到 CSRF 的攻击。除非您使用 SameSite=Lax 或 Strict 的 cookie。或者我是这么想的。但是,如果你有 XSS 漏洞,那你也可以vulnerable to CSRF。 ...因此,我现在认为没有明确的赢家。一方面我可以说,为什么不将令牌存储在本地存储中?将其存储在 cookie 中可能会受到 CSRF 的攻击。除非您确定自己不会受到 XSS 攻击并且 cookie 具有 SameSite 属性。另一方面,无论如何,您必须采取措施防止 XSS。因此,将其存储在本地存储中更有意义。因为你甚至不必考虑 CSRF。关于此事的good answer。以上是关于身份验证:JWT 使用与会话的主要内容,如果未能解决你的问题,请参考以下文章