在此下载 JSON Web 令牌 (JWT) 不安全的电子书
有时,人们会采用旨在解决狭窄问题的技术,并开始广泛应用它们。问题可能看起来相似,但利用独特的技术来解决一般问题可能会产生意想不到的后果。打个比方,如果你是锤子,那么一切都像钉子一样。JWT 就是这样一项技术。
来自 Okta 等公司的 SME 有许多深入的文章和视频,讨论了使用 JWT 令牌的潜在危险和低效[1]。然而,这些警告被营销人员、YouTuber、博主、课程创建者以及其他明知故犯或不知情地推广它的人所掩盖。
如果您查看这些视频和文章中的许多,他们只谈论 JWT 的感知优势,而忽略了其不足。更具体地说,他们只展示了如何使用它,但没有谈论在真实生产环境中 JWT 添加的撤销和其他复杂性。他们也从未将其与现有的经过实战检验的方法进行足够深入的比较,以便真正权衡利弊。
或者,也许是完美、热门且友好的名称导致了它的流行。“JSON”(普遍受欢迎)、“Web”(针对网络)和“Token”(暗示无状态)使人们认为它非常适合他们的网络身份验证工作。
所以,我认为这是一个营销胜过工程师和安全专家的案例。但并非全是坏事,因为在 Hacker News 上经常进行关于 JWT 的长期且热烈的辩论(参见 这里、这里 和 这里),所以还是有希望的。
如果您仔细想想,这些持续不断的辩论本身就应该是一个警钟,因为您不应该看到这样的辩论,尤其是在安全领域。安全应该是二元的。要么技术安全,要么不安全。
无论如何,在这篇博文中,我想重点关注使用 JWT 的潜在危险,并讨论一种已经存在十年的经过实战检验的解决方案。
为了更好地理解,当我谈论 JWT 时,我指的是“无状态 JWT”,这是 JWT 流行起来的主要原因,也是使用 JWT 的最大原因。此外,我在下面的资源部分列出了所有其他深入探讨 JWT 的文章。
在我们了解为什么它很危险之前,让我们先通过一个示例用例来了解它的工作原理。
想象一下,您正在使用 Twitter。您登录、撰写推文、点赞推文,然后转发他人的推文。因此,您执行了四项操作。对于每个操作,您都需要在执行该特定操作之前进行身份验证和授权。
以下是传统方法中发生的情况。
问题在于,步骤四很慢,并且需要为用户执行的每个操作重复执行。因此,每个 API 调用都会导致至少两次缓慢的数据库调用,这会减慢整体响应时间。
有不同的方法可以实现这一点。
现在,让我们看看 JWT 的方式。
JWT,尤其是当用作会话时,试图通过完全消除数据库查找来解决问题。
主要思想是将用户的相关信息存储在会话令牌本身中!因此,与其使用某个长的随机字符串,不如将实际用户的相关信息存储在会话令牌本身中。为了安全起见,令牌的一部分使用服务器独有的密钥进行签名。
因此,即使客户端和服务器都可以看到令牌的用户相关信息部分,但第二部分,即签名部分,只能由服务器验证。
在下图中,令牌的粉色部分包含有效负载(用户信息),客户端或服务器都可以看到。
但蓝色部分使用密钥字符串、标头和有效负载本身进行签名。因此,如果客户端篡改有效负载(比如假冒其他用户),签名将不同,并且无法进行身份验证。
以下是使用 JWT 的用例的样子
将来,对于每个用户操作,服务器只需验证签名部分,获取用户信息,并让用户执行该操作。从而完全跳过了数据库调用。
但是,关于 JWT 令牌,还有一件需要了解的重要事项。那就是它使用过期时间来自我过期。它通常设置为 5 分钟到 30 分钟。由于它是自包含的,因此您无法轻松地撤销/使它无效/更新它。这确实是问题的关键所在。
JWT 最大的问题是令牌撤销问题。由于它会一直有效,直到过期,因此服务器没有简单的方法可以撤销它。
以下是使这变得危险的一些用例。
想象一下,你在发完推文后从 Twitter 注销了。你可能会认为你已经从服务器注销了,但事实并非如此。因为 JWT 是自包含的,并且会一直有效,直到它过期。这可能是 5 分钟或 30 分钟,或者任何在令牌中设置的持续时间。因此,如果有人在这段时间内获得了该令牌,他们就可以继续访问它,直到它过期。
想象一下,你是 Twitter 或某些在线实时游戏的管理员,真正的用户正在使用该系统。作为管理员,你想快速阻止某人滥用系统。你做不到,同样是因为同样的原因。即使你封锁了用户,他们仍然可以继续访问服务器,直到令牌过期。
想象一下,该用户是管理员,被降级为普通用户,权限较少。同样,这不会立即生效,用户将继续保持管理员身份,直到令牌过期。
据发现,多年来,许多实现 JWT 的库存在许多安全问题,甚至规范本身也存在安全问题。甚至 Auth0 本身,他们推广 JWT,也遇到了一个问题。
在许多复杂的现实世界应用中,你可能需要存储大量不同的信息。将信息存储在 JWT 令牌中可能会超过允许的 URL 长度或 Cookie 长度,从而导致问题。此外,你可能会在每次请求时发送大量数据。
在许多现实世界应用中,服务器必须维护用户的 IP 并跟踪 API 以进行限速和 IP 白名单。因此,无论如何你都需要使用一个极快的数据库。认为你的应用通过 JWT 就能变得无状态是不现实的。
一个流行的解决方案是在数据库中存储一个“已撤销令牌”列表,并在每次调用时检查它。如果令牌属于该撤销列表,则阻止用户执行下一步操作。但这样你又需要额外调用数据库来检查令牌是否被撤销,这完全违背了 JWT 的目的。
虽然 JWT 消除了对数据库的查找,但它在这样做时引入了安全问题和其他复杂性。安全是二元的——要么安全,要么不安全。因此,使用 JWT 进行用户会话很危险。
在后端进行服务器到服务器(或微服务到微服务)通信时,可以使用 JWT 令牌。一个服务可以生成 JWT 令牌,将其发送到另一个服务以进行授权。以及其他一些狭窄的地方,比如重置密码,你可以发送一个 JWT 令牌作为一次性的短期令牌来验证用户的电子邮件。
解决方案是不将 JWT 用于会话。而是使用传统但经过考验的方式,以更有效的方式进行。
也就是说,让数据库查找非常快(亚毫秒级),这样额外的调用就不会造成影响。
是否存在如此快的数据库,能够以亚毫秒级速度处理数百万个请求?
当然有。它被称为 Redis!每天为数十亿用户提供服务的数千家公司使用 Redis 来实现这一目的!
Redis Enterprise 是 Redis OSS 的增强版,提供 99.999% 的可用性,可以处理数万亿个请求。它可以作为私有云上的免费软件或在任何三大云中的云上使用。
更重要的是,Redis Enterprise 已经从简单的缓存或会话存储发展成为一个功能齐全的多模型数据库,其模块生态系统与核心 Redis 本地运行。 例如,你可以使用JSON(比市场领先者快 10 倍),本质上拥有一个实时类似 MongoDB 的数据库,或者使用搜索和查询功能(比 Algolia 快 4-100 倍)并实现实时全文搜索。
如果你只是使用 Redis 作为会话存储,并使用其他数据库作为主数据库,那么你的架构将如下所示。 需要注意的是,Redis Enterprise 提供四种类型的缓存:缓存旁路(延迟加载)、写后(写回)、写直通和读副本,而 Redis OSS 只提供一种(缓存旁路)。
注意,闪电 emoji 代表极快的速度。蜗牛 emoji 代表慢速。
如前所述,你也可以使用 Redis 作为整个数据层的主数据库。在这种情况下,你的架构会变得更简单,基本上所有东西都变得非常快。
当然,公司使用 Redis 不仅仅作为独立的数据库,而且作为地理分布式数据库集群。