dot 快速的未来即将在您所在的城市举办的活动中到来。

加入我们在 Redis 发布会

JSON Web 令牌 (JWT) 对用户会话来说很危险——解决方案

在此下载 JSON Web 令牌 (JWT) 不安全的电子书

有时,人们会采用旨在解决狭窄问题的技术,并开始广泛应用它们。问题可能看起来相似,但利用独特的技术来解决一般问题可能会产生意想不到的后果。打个比方,如果你是锤子,那么一切都像钉子一样。JWT 就是这样一项技术。

来源:“停止将 JWT 用于会话”(有关链接,请参见下面的参考资料)。
sessions
来源:“为什么 JWT 对身份验证不利”——Randall Degges,Okta 开发者宣传负责人。
not default
来源:“JWT 不应该成为您会话的默认设置”(有关链接,请参见下面的参考资料)。
Ptacek

来自 Okta 等公司的 SME 有许多深入的文章和视频,讨论了使用 JWT 令牌的潜在危险和低效[1]。然而,这些警告被营销人员、YouTuber、博主、课程创建者以及其他明知故犯或不知情地推广它的人所掩盖。 

如果您查看这些视频和文章中的许多,他们只谈论 JWT 的感知优势,而忽略了其不足。更具体地说,他们只展示了如何使用它,但没有谈论在真实生产环境中 JWT 添加的撤销和其他复杂性。他们也从未将其与现有的经过实战检验的方法进行足够深入的比较,以便真正权衡利弊。 

或者,也许是完美、热门且友好的名称导致了它的流行。“JSON”(普遍受欢迎)、“Web”(针对网络)和“Token”(暗示无状态)使人们认为它非常适合他们的网络身份验证工作。

所以,我认为这是一个营销胜过工程师和安全专家的案例。但并非全是坏事,因为在 Hacker News 上经常进行关于 JWT 的长期且热烈的辩论(参见 这里这里),所以还是有希望的。 

如果您仔细想想,这些持续不断的辩论本身就应该是一个警钟,因为您不应该看到这样的辩论,尤其是在安全领域。安全应该是二元的。要么技术安全,要么不安全。

无论如何,在这篇博文中,我想重点关注使用 JWT 的潜在危险,并讨论一种已经存在十年的经过实战检验的解决方案。 

为了更好地理解,当我谈论 JWT 时,我指的是“无状态 JWT”,这是 JWT 流行起来的主要原因,也是使用 JWT 的最大原因。此外,我在下面的资源部分列出了所有其他深入探讨 JWT 的文章。

在我们了解为什么它很危险之前,让我们先通过一个示例用例来了解它的工作原理。

用例

想象一下,您正在使用 Twitter。您登录、撰写推文、点赞推文,然后转发他人的推文。因此,您执行了四项操作。对于每个操作,您都需要在执行该特定操作之前进行身份验证和授权。 

以下是传统方法中发生的情况。

传统方法

  1. 您使用用户名和密码登录
    1. 服务器首先验证用户。
    2. 然后服务器创建一个会话令牌,并将该令牌与用户的信息一起存储在某个数据库中。(注意:会话令牌是一个长的不可识别的字符串——也称为不透明字符串——它看起来像这样:fsaf12312dfsdf364351312srw12312312dasd1et3423r)
  2. 然后服务器将会话令牌发送到前端移动或 Web 应用程序。
    1. 然后此令牌存储在应用程序的 cookie 或本地存储中。
  3. 接下来,假设您撰写并提交了一条推文。然后,除了您的推文之外,您的应用程序还会发送会话令牌(通过 cookie 或标头),以便服务器可以识别您的身份。但是令牌只是一个随机字符串,那么服务器如何仅仅通过会话令牌就能知道您的身份呢? 
  4. 当服务器收到会话令牌时,它不会知道用户是谁,因此会将其发送到数据库以检索(4a)该令牌中实际用户的相关信息(如 userId)。 
  5. 如果用户存在且被允许执行该操作(即发送推文),服务器将允许他们执行该操作。
  6. 最后,它告诉前端已发送推文。
Grey 2

传统方法的主要问题

问题在于,步骤四很慢,并且需要为用户执行的每个操作重复执行。因此,每个 API 调用都会导致至少两次缓慢的数据库调用,这会减慢整体响应时间。 

有两种方法可以解决此问题

  1. 以某种方式完全消除对用户的数据库查找(即消除步骤四)。 
  2. 使额外的数据库查找速度更快,以便额外的跳跃无关紧要。 

选项 1:消除数据库查找(步骤 4)

有不同的方法可以实现这一点。 

  1. 您可以将状态存储在服务器的内存中。但这会导致在您进行扩展时出现问题,因为此状态仅在特定服务器上可用。 
  2. 使用“粘性会话”。在这里,您告诉负载均衡器始终将流量定向到特定服务器,即使您进行了扩展也是如此。这又会导致不同的扩展问题,如果服务器停止运行(缩减规模),您将丢失所有状态。
  3. 第三种选择是 JWT。我们接下来将研究这一点。

现在,让我们看看 JWT 的方式。

JWT 的方式

JWT,尤其是当用作会话时,试图通过完全消除数据库查找来解决问题。 

主要思想是将用户的相关信息存储在会话令牌本身中!因此,与其使用某个长的随机字符串,不如将实际用户的相关信息存储在会话令牌本身中。为了安全起见,令牌的一部分使用服务器独有的密钥进行签名。

因此,即使客户端和服务器都可以看到令牌的用户相关信息部分,但第二部分,即签名部分,只能由服务器验证。 

在下图中,令牌的粉色部分包含有效负载(用户信息),客户端或服务器都可以看到。 

但蓝色部分使用密钥字符串、标头和有效负载本身进行签名。因此,如果客户端篡改有效负载(比如假冒其他用户),签名将不同,并且无法进行身份验证。

Encoded

以下是使用 JWT 的用例的样子

  1. 您使用用户名和密码登录
    1. 服务器通过查询数据库来验证用户
    2. 然后服务器使用用户的相关信息和密钥创建一个 JWT 会话令牌(不涉及数据库)
  2. 然后服务器将 JWT 令牌发送到前端应用程序。对于将来的活动,用户只需发送 JWT 令牌来识别用户,而不必每次都登录。
    1. JWT 令牌看起来像这样:<header>.<payload>.<signature>
  3. 接下来,假设您撰写并提交了一条推文。当您发送它时,除了您的推文文本之外,您的应用程序还会发送 JWT 令牌(通过 cookie 或标头),以便服务器可以识别您的身份。但是服务器如何仅仅通过 JWT 令牌就能知道您的身份呢?好吧,令牌的一部分已经包含用户信息。
  4. 因此,当服务器收到 JWT 令牌时,它使用密钥验证签名部分,并从有效负载部分获取用户信息。从而消除了数据库调用。
  5. 如果签名验证通过,则允许他们执行该操作。
  6. 最后,将推文已保存的消息发送到前端

将来,对于每个用户操作,服务器只需验证签名部分,获取用户信息,并让用户执行该操作。从而完全跳过了数据库调用。

Grey 1

令牌过期

但是,关于 JWT 令牌,还有一件需要了解的重要事项。那就是它使用过期时间来自我过期。它通常设置为 5 分钟到 30 分钟。由于它是自包含的,因此您无法轻松地撤销/使它无效/更新它。这确实是问题的关键所在。

那么,为什么 JWT 对用户身份验证来说很危险呢?

JWT 最大的问题是令牌撤销问题。由于它会一直有效,直到过期,因此服务器没有简单的方法可以撤销它。

以下是使这变得危险的一些用例。

  1. 注销实际上并没有注销您!

想象一下,你在发完推文后从 Twitter 注销了。你可能会认为你已经从服务器注销了,但事实并非如此。因为 JWT 是自包含的,并且会一直有效,直到它过期。这可能是 5 分钟或 30 分钟,或者任何在令牌中设置的持续时间。因此,如果有人在这段时间内获得了该令牌,他们就可以继续访问它,直到它过期。

  1. 封锁用户不会立即阻止他们。

想象一下,你是 Twitter 或某些在线实时游戏的管理员,真正的用户正在使用该系统。作为管理员,你想快速阻止某人滥用系统。你做不到,同样是因为同样的原因。即使你封锁了用户,他们仍然可以继续访问服务器,直到令牌过期。

  1. 可能会出现过时数据

想象一下,该用户是管理员,被降级为普通用户,权限较少。同样,这不会立即生效,用户将继续保持管理员身份,直到令牌过期。

  1. JWT 通常没有加密,因此任何能够执行中间人攻击并嗅探 JWT 的人现在都拥有你的身份验证凭据。这很容易实现,因为 MITM 攻击只需要在服务器和客户端之间的连接上完成。 

其他复杂性和注意事项

库和规范问题

据发现,多年来,许多实现 JWT 的库存在许多安全问题,甚至规范本身也存在安全问题。甚至 Auth0 本身,他们推广 JWT,也遇到了一个问题

令牌长度

在许多复杂的现实世界应用中,你可能需要存储大量不同的信息。将信息存储在 JWT 令牌中可能会超过允许的 URL 长度或 Cookie 长度,从而导致问题。此外,你可能会在每次请求时发送大量数据。

无论如何都需要维护状态(用于限速、IP 白名单等)。

在许多现实世界应用中,服务器必须维护用户的 IP 并跟踪 API 以进行限速和 IP 白名单。因此,无论如何你都需要使用一个极快的数据库。认为你的应用通过 JWT 就能变得无状态是不现实的。 

建议的解决方法

一个流行的解决方案是在数据库中存储一个“已撤销令牌”列表,并在每次调用时检查它。如果令牌属于该撤销列表,则阻止用户执行下一步操作。但这样你又需要额外调用数据库来检查令牌是否被撤销,这完全违背了 JWT 的目的。 

底线

虽然 JWT 消除了对数据库的查找,但它在这样做时引入了安全问题和其他复杂性。安全是二元的——要么安全,要么不安全。因此,使用 JWT 进行用户会话很危险。

在哪里可以使用它?

在后端进行服务器到服务器(或微服务到微服务)通信时,可以使用 JWT 令牌。一个服务可以生成 JWT 令牌,将其发送到另一个服务以进行授权。以及其他一些狭窄的地方,比如重置密码,你可以发送一个 JWT 令牌作为一次性的短期令牌来验证用户的电子邮件。

如果不能使用 JWT,还能做些什么?

解决方案是不将 JWT 用于会话。而是使用传统但经过考验的方式,以更有效的方式进行。

也就是说,让数据库查找非常快(亚毫秒级),这样额外的调用就不会造成影响。

选项 2:让查找速度快到不会造成影响(经过考验的解决方案) 

是否存在如此快的数据库,能够以亚毫秒级速度处理数百万个请求? 

当然有。它被称为 Redis!每天为数十亿用户提供服务的数千家公司使用 Redis 来实现这一目的!

Redis Enterprise 是 Redis OSS 的增强版,提供 99.999% 的可用性,可以处理数万亿个请求。它可以作为私有云上的免费软件或在任何三大云中的云上使用。 

更重要的是,Redis Enterprise 已经从简单的缓存或会话存储发展成为一个功能齐全的多模型数据库,其模块生态系统与核心 Redis 本地运行。  例如,你可以使用JSON比市场领先者快 10 倍),本质上拥有一个实时类似 MongoDB 的数据库,或者使用搜索和查询功能比 Algolia 快 4-100 倍)并实现实时全文搜索。

如果你只是使用 Redis 作为会话存储,并使用其他数据库作为主数据库,那么你的架构将如下所示。  需要注意的是,Redis Enterprise 提供四种类型的缓存:缓存旁路(延迟加载)、写后(写回)、写直通和读副本,而 Redis OSS 只提供一种(缓存旁路)。

注意,闪电 emoji 代表极快的速度。蜗牛 emoji 代表慢速。

Session JWT

如前所述,你也可以使用 Redis 作为整个数据层的主数据库。在这种情况下,你的架构会变得更简单,基本上所有东西都变得非常快。

Db JWT

它可以扩展吗?

当然,公司使用 Redis 不仅仅作为独立的数据库,而且作为地理分布式数据库集群。

[1] 参考资料

  1. 停止使用 JWT 进行会话
  2. JWT 不应该是你的会话默认选择
  3. 为什么 JWT 对身份验证不利——Randall Degges(Okta 开发关系主管)
  4. 停止使用 JWT 进行会话,第 2 部分:为什么你的解决方案不起作用
  5. Thomas H. Ptacek 在 Hacker News 上的评论
  6. 我使用 JSON Web Token 的经验
  7. 网络身份验证(会话、Cookie、JWT、localStorage 等等)
  8. Thomas H. Ptacek 的博客