理解和使用JSON Web Tokens(JWT)进行Web应用程序授权
理解和使用JSON Web Tokens(JWT)进行Web应用程序授权
如今,Web令牌 是在网络中进行授权的一种非常流行的方式。JWT在Microsoft的背景下也变得非常流行,并且对我们今天构建应用程序的方式产生了一些其他影响。在本篇笔记中,我们将学习JWT是什么,以及如何在保护Web应用程序的上下文中具体使用它。JWT的全称通常发音为"jot",顺便提一句,这就像添加额外的"a"一样,也就是"jabbin"。然而,我发现在社区中,对JWT存在一些争议。但是在本篇笔记中,我们将把它称为Jabber。
虽然Jabber通常用于管理授权,但其背后的理念是创建两方之间安全通信的标准方法。因此,有这个名为RFC7519的开放行业标准规范,它概述了JWT应该如何结构化以及如何将其用于扩展信息,通常称为payload。但是,尽管JWT也广泛用于授权,但我们在本篇笔记中将把重点放在这一方面。
所以,当涉及到授权时,除了传统的基于会话令牌的授权机制之外,还有一堆其他选项,包括基于会话文档的授权、基于Jabber的授权或者未来可能想到的任何其他机制。它们都有一个共同点,那就是SDP。你看,HTTP是一种无状态协议。这意味着每个HTTP交互都需要包含进行该交互所需的所有信息。没有东西是从前面记住的,没有状态在多个请求之间保持。想象一下,当你从服务器访问一个页面时,你需要发送什么信息呢?嗯,如果是一个简单的静态页面,那么每个请求都应该包含足够的信息以便HTTP协议处理。所以,如果服务端应用程序是静态的,而且对所有人都可用,那就没有问题。问题是,当服务器的响应是动态的,并且取决于用户是谁时。在这种情况下,你发送给服务器的信息不仅仅是你想要的页面。显然,你还需要告诉服务器你是谁。
所以,我们有一个服务器应用程序,其中的页面P1和P2只能被某些用户访问。你告诉服务器活动用户A,我喜欢页面P1。服务器会说,好的,这是页面P1。但是接着,假设你还想要另一个页面,你会说,嘿,谢谢,我还能得到P2吗?但是这一次事情就不一样了,因为服务器不知道你是谁,因为它不记得你之前的请求。在每次交互中,你都必须提供所有需要的细节和信息。所以你又要告诉服务器你是活动用户,以及你想要的页面。服务器才知道该做什么,因为用于执行任务的所有信息都包含在请求中,不依赖于先前的信息。
你可能会说,等一下,这和我使用的Web应用程序的体验不一样。例如,你可以使用用户名和密码登录到银行网站。网站会说,好的,认证通过了,然后让你进入账户页面。网站不会问,你是谁?不会这样。网站知道你是谁,直到你注销登录,对吗?它是如何做到的呢?Web应用程序管理和记住会话的方式有很多种,其中两种流行的方式是使用会话令牌和使用JSON。就像我要讲的客服请求的类比一样,客户部门和支持部门都知道客户的问题,并且解决方案是。代表尝试了一些解决步骤,但没有用。于是他说,好的,我转接到另一个部门,他们可以帮忙。明天请回电话。这位支持代表记录了所有细节,包括所有的故障排除步骤,并将其保存在系统中,并为客户生成了一个支持票证。这个票证号码与支持代表保存在系统中的所有细节相关联。所以下次客户再次来时,他不需要重新解释所有细节,不需要重复相同的步骤,也不需要再次拨打电话。他该怎么做呢?他只需要给出相同的票证号码。新的支持代表可能是今天来的,也可能是以前的支持代表,他不记得以前的对话。他查找票证号码并获取在系统中保存的所有细节。这就是使用会话令牌进行身份验证的情况。当你被识别后,服务器创建一个会话并跟踪它。它创建一个会话ID与该会话关联。然后将该ID提供给你,就像我们刚刚看到的示例中的支持票证一样。因此,随后,客户端将此令牌作为请求的一部分传递给服务器,服务器查找并识别客户端。服务器通常同时为多个客户端提供服务,因此让客户端传递会话ID很方便。服务器知道客户端是谁,并且可以根据单个令牌查找信息。
现在客户端如何将会话ID传递给服务器实际上取决于实现的方式,但最常见的方法是将会话保存在cookie中,以便它自动添加到cookie标头中。随后的请求会自动在cookie标头中携带会话ID,因为浏览器会这样做。所以服务器有了这些信息,并且可以再次查找以识别客户端。这种将会话ID保存为令牌并在cookie中传递的机制已经运行良好一段时间了,而且就像我所说的,这可能是授权的最流行机制。不过,这种方法也存在一些问题,这就是JWT出现的原因。要了解问题,我们首先需要了解一件事情,即它假设了什么。它假设总是只有一个单一的服务器应用程序。过去通常是这样,但如今已经不再是这样了。现代的应用程序看起来不是这样的。它们看起来是像……你有多个服务共享负载。它们在负载均衡器的后面。当请求到来时,负载均衡器决定将请求发送到哪个服务器。所以这里的问题是,服务器可能会将它们的登录请求路由到服务器1,而会话则保存在服务器1的内存中。接下来的请求经过负载均衡器,到达了服务器2。现在服务器2对这个先前的交换一无所知,因为只有服务器1才能识别并查找到会话ID令牌。所以解决方案很明显,你需要引入一个共享会话缓存,所有这些服务器都将会话保存到其中,并从中查找会话令牌。这是使用共享缓存的典型用例,比如使用缓存集群,这也是其中的一个解决方案,因为这样做存在一个单点故障。现在如果这个缓存实例崩溃了,所有的会话都会中断,这就是为什么有些实现会采取不同的方法,它们采取的方法是遵循粘性会话模式,基本上是负载均衡器记住了哪个服务器给了用户会话。它总是将用户的下一个请求发送到该特定的服务器。是的,这并不是很可扩展。此外,在微服务的情况下,如果有多个服务彼此协作,会话信息如何在所有这些不同的微服务实例之间传递?这是棘手的。
好了,现在让我们提出一个替代模型。你还记得我告诉过你的客户服务的类比吗?让我们假设服务员不维护状态,设想一下服务设备。没有网络,也没有其他任何东西。让我们说客户必须走到服务部门,代理问他出了什么问题,他告诉了他们,服务员说,好的,我们会处理的,明天再来吧。但是想象一下,你是服务代表,你不希望这个客户在明天再次来时向另一个代表重复他的全部故事,对吧?那么你会怎么做,以便让这对客户更加轻松?你没有任何存储,也没有什么可以在你这端保存这次交互的东西。给了个案例编号,你说得对。所以你会怎么做呢?这里是一个想法。与其在系统中注册案例并给客户一个案例编号,而在这种情况下你做的是你在一张纸上写下所有的交互细节,然后把它递给客户,并说,好的,下次你再来的时候带上这张纸,交给你谈话的客户应用程序,他们可以阅读这个并理解并获取所有的细节。所以这与以前的模型有所不同。客户代表给客户一个令牌ID来引用细节,客户回复是直接给客户细节本身。如果这样做,客户需要记住什么呢,虽然对于后续与支持部门的每次交互,客户都有责任获取这张纸。支持部门不需要记住任何东西。这是好的,但这种方法也有缺点。
比如说,如果客户拿着一张历史问题的纸来了,支持部门怎么知道它是可信的?可能有一位恶意的客户拿着一张纸,写下了完整的不良客户服务历史,然后要求免费的补偿。这是个坏事,你知道的,这很困难。你需要让记录的历史值得信赖。那么一个解决办法是给你递交给客户的纸上签名,支持者可以安全地签署所交给客户的信息,并在客户下次拿到时验证新的支持代表会检查签名以确保其有效。这种模式转变是JWT中的隐含机制。想象一下,当客户端认证时,服务器不是将其信息保存在令牌上并将其作为令牌返回,而是将信息本身作为令牌返回,对吧?想象一下,每次客户端发出后续请求时,客户端都会发送包含用户信息的令牌。服务器不保存任何东西。每次请求到来时,服务器会说,好的,让我们看看这是谁。嗯,这个令牌说这个用户名是谁,并且他们已经成功认证。好吧,让他进来吧。所以这个令牌不是一个ID,而是一个JSON对象,其中包含所有信息。这,朋友们,就是所谓的JSON Web Tokens,也就是JWT。当然,安全问题在这里得到了处理,通过对每次用户访问时传递的文档进行签名来确保安全性。当客户端发送后续请求时,服务器会验证签名并信任它。只有在签名有效时,其中包含服务器需要的所有信息。
这其实就是JWT的全部内容,它是一个允许客户端和服务器直接通信并共享信息的方式,而无需服务器为每次交互记住信息。它之所以在客户端和服务器之间,用于授权目的,其实是个巧合,它实际上可以用于任何事情,例如,你几乎可以用JWT的格式发放邀请,并在派对入口验证JWT的签名以确保只有受邀者才能进入。所以在授权的上下文中,如果你把会话ID和JWT进行对比,可以把会话ID令牌看作是一个引用令牌,它引用了一个状态,而JWT则是一个值令牌,它包含了值本身。请注意,我们一直在谈论令牌的值以及令牌是什么,而不是它是如何发送的。就像会话ID可以通过cookie发送一样,JWT也可以通过cookie发送。如果你想将其保存在本地存储中并发送它,浏览器也可以。会话令牌和JWT令牌只是用于交换的不同选项,取决于你的需要。所以,这都取决于你。
好的,现在JWT是什么样子呢?每次请求中都要额外发送一个JSON对象,看起来可能有些痛苦。嗯,实际上它看起来并不像一个JSON对象,它更像是一个结构和过程,用于创建一个JWT。实际上,你可以看到你屏幕上的这个令牌,然后查看JSON载荷和头部是什么,来检查JWT的结构。在下一个教程中,我将详细解释JWT的结构,并告诉你如何传递JWT,以及如何自己创建新的JWT。我们还将研究与会话ID相比,JWT的一些缺点。
