原始地址:https://dev.to/zachgoll/the-ultimate-guide-to-passport-js-k2l
这篇文章也可以在[YouTube系列](https://www.youtube.com/playlist?list=PLYQSCk-qyTW2ewJ05f_GKHtTIzjynDgjK)中查看。在这篇文章中,我将讲解为什么Passport-JWT身份验证策略是一个简单、安全的解决方案,适用于使用Node/Express + Angular网页应用程序的小团队和初创企业。为了理解为什么在这种情况下JWT身份验证流是最佳选择,我将带您了解可用的身份验证选项、它们的工作原理以及如何实施它们(不包括OAuth,因为超出了本文范围)。由于本文较长且详细,如果您对讨论的某个主题已经熟悉,可以跳过它。同样地,如果您只是想了解如何实现特定的身份验证方法,可以直接跳转到下面的相应部分:
同时,我创建了两个示例应用程序,分别使用了上述两种身份验证方法:
点击此处了解更多身份验证信息。
上面是当前面向开发人员的主要身份验证选择的高级概述。以下是每个选择的简要概述:
- 基于会话的身份验证-使用浏览器Cookie和后端“会话”来管理已登录和已注销的用户。
- JWT身份验证-一种无状态的身份验证方法,其中将JSON Web令牌(JWT)存储在浏览器中(通常是localStorage)。此JWT对用户有所断言,只能使用存储在服务器上的秘密进行解码。
- OAuth和OpenID Connect身份验证-一种现代的身份验证方法,其中应用程序使用从其他应用程序生成的“声明”来验证其自己的用户。换句话说,这是一种联合身份验证,其中现有服务(如Google)处理用户的身份验证和存储,而您的应用程序利用此流程来对用户进行身份验证。
请注意,OAuth可能很快变得混乱,因此本文不会对其进行全面讨论。对于一个初创团队/创业公司来说,OAuth不仅不必要,而且在使用不同的服务(如Google、Facebook、Github等)时也具有高度的可变性。
最后,您可能会注意到OAuth被列为“作为服务”和“内部”。这是一个特定意见,以突出实际上有一个名为“OAuth”的公司,并实施了OAuth协议…作为一个服务。您可以在不使用OAuth公司的服务的情况下实施OAuth协议!
点击此处了解什么是基于会话的身份验证?
如果我们要为这些身份验证方法创建一个谱系,基于会话的身份验证将是最早的方法之一,但肯定不会过时。该身份验证方法是“服务器端”,这意味着我们的Express应用程序和数据库共同工作,以保持访问我们的应用程序的每个用户的当前身份验证状态。
为了了解基于会话的身份验证的基本原则,您需要了解一些概念:
- 基本HTTP标头协议
- Cookie是什么
- 会话是什么
- 会话(服务器)和Cookie(浏览器)如何交互以验证用户
点击此处了解HTTP标头?
在浏览器中进行HTTP请求有很多方式。HTTP客户端可以是Web应用程序、物联网设备、命令行(curl)或其他多种方式。这些客户端连接到Internet并发出HTTP请求,以获取数据(GET)或修改数据(POST、PUT、DELETE等)。
为了解释的目的,让我们假设:
- 服务器 = www.google.com
- 客户端 = 在咖啡店里使用笔记本电脑工作的随机人
当那个来自咖啡店的随机人将www.google.com键入他的Google Chrome浏览器时,这个请求将带有“HTTP标头”。这些HTTP标头是提供附加数据给浏览器以帮助完成请求的键值对。此请求将有两种类型的标头:
- 通用标头
- 请求标头
为了实现互动性,打开Google Chrome,打开开发者工具(右键单击,“检查元素”),点击“网络”标签。现在,在地址栏中键入www.google.com,并观察网络标签从服务器加载多个资源。您应该看到一些列名称、状态、类型、初始化者、大小、时间和瀑布等列。找到“类型”值为“document”的请求,并点击它。您应该在此请求和响应交互的所有标头中看到所有标头。
您作为客户端的请求将具有以下General和Request标头(将不是完全相同):
- General Headers
- Request URL: https://www.google.com/
- Request Method: GET
- Request Headers
- Accept: text/html
- Accept-Language: en-US
- Connection: keep-alive
在输入您的地址栏中输入www.google.com并按回车后,您的HTTP请求会带着这些标头(可能还有其他一些)发送(这些标头相对容易理解,我希望逐一解释一些更多的内容,以更好地了解HTTP标头的用途,请随意在MDN上查询您不熟悉的任何内容):
General标头可以是请求和响应数据的混合。很明显,Request URL和Request Method是请求对象的一部分,它们告诉Google Chrome浏览器请求的路由应该如何处理。Status Code显然是响应的一部分,因为它表明GET请求成功,网页www.google.com正常加载。
Request Headers仅包含与请求对象本身一起包含的标头。您可以将请求标头视为“服务器的指令”。在这种情况下,我的请求向Google服务器发出以下请求:
- 嘿,Google服务器,请向我发送HTML或文本数据。我目前无法或对无关的内容不感兴趣!
- 嘿,Google服务器,请只向我发送英文单词
- 嘿,Google服务器,请求结束后请不要关闭连接
您可以设置许多其他请求标头,但这些只是你可能在所有HTTP请求中看到的一些常见标头。
因此,当您搜索www.google.com时,您发送了您的请求和标头到Google服务器(为简单起见,我们将假设它是一个大型服务器)。Google服务器接受了您的请求,阅读了“指令”(标头),并创建了一个响应。这个响应由以下内容组成:
- HTML数据(在浏览器中看到的内容)
- HTTP标头
您可能已经猜到,“响应标头”是Google服务器设置的标头。下面是您可能会看到的一些响应标头:
- Response Headers
- Content-Length: 41485
- Content-Type: text/html; charset=UTF-8
- Set-Cookie: made_up_cookie_name=some value; expires=Thu, 28-Dec-2020 20:44:50 GMT;
在上面的标头中,除了Set-Cookie标头之外,都相对容易理解。我将包含Set-Cookie标头,因为这正是我们需要了解的,以了解基于会话的身份验证是什么(并且将帮助我们稍后理解本文中的其他身份验证方法)。
点击此处了解Cookie是如何工作的?
如果浏览器中没有Cookie,我们就会遇到问题。
如果我们有一个所保护的网页,我们希望用户登录以访问它,那么如果没有Cookie,这些用户必须每次刷新页面时都要重新登录!这是因为HTTP协议默认是“无状态”的。
Cookie引入了“持久状态”的概念,并允许浏览器“记住”服务器之前告诉它的内容。
Google服务器可以告诉我的Google Chrome浏览器允许我访问受保护的页面,但是当我刷新网页时,我的浏览器将“忘记”这一点,并要求我再次进行身份验证。
这就是Cookie的用途,并解释了Set-Cookie标头是什么。在我们上面的请求中,我们在浏览器中键入www.google.com并按回车键,我们的客户端发送了一个带有一些标头的请求,而Google服务器则带有一个响应和几个标头做出响应。其中一个响应标头是Set-Cookie: made_up_cookie_name=some value; expires=Thu, 28-Dec-2020 20:44:50 GMT;。以下是此交互如何工作:
- 服务器:“嘿,客户端!我想让你设置名为made_up_cookie_name的cookie,并将其设置为some value。”
- 客户端:“嘿,服务器,我将在我的Cookie请求头中为这个域设置一个Cookie,直到2020年12月28日为止!”
我们可以在Google Chrome Developer Tools中验证这一点。转到“应用程序”->“存储”并单击“Cookie”。现在点击您当前正在访问的网站,您将看到为此网站设置的所有cookie。在我们的假示例中,您可能会看到这样的内容:
Name | Value | Expires / Max-Age |
---|---|---|
made_up_cookie_name | some value | 2020-12-28T20: 44: 50.674Z |
现在,这个cookie将在之前设置的到期日期之前,在www.google.com的所有请求中设置到Cookie请求头上。
您可能会得出的结论是,如果我们设置某种“auth” cookie,这对于身份验证可能非常有用。一个过于简化的过程可能是:
- 一个匿名的用户输入www.example-site.com/login/到浏览器中
- 匿名的用户在此页面上填写一个包含用户名和密码的表单
- 匿名用户的Google Chrome浏览器将一个带有登录数据(用户名、密码)的POST请求提交给正在运行的服务器www.example-site.com。
- 运行www.example-site.com的服务器接收登录信息,检查数据库是否存在该登录信息,验证登录信息,如果成功,创建一个带有Set-Cookie标头的响应:user_is_authenticated=true; expires=Thu, 1-Jan-2020 20:00:00 GMT。
- 匿名用户的Google Chrome浏览器接收此响应,并设置一个浏览器cookie:
Name | Value | Expires / Max-Age |
---|---|---|
user_is_authenticated | true | 2020-12-28T20:44:50.674Z |
- 匿名用户现在访问www.example-site.com/protected-route/
- 匿名用户的浏览器为HTTP请求创建一个带有Cookie标头的请求:user_is_authenticated=true; expires=Thu, 1-Jan-2020 20:00:00 GMT。
- 运行服务器收到此请求,看到请求上有一个cookie,“记住”它几秒钟前身份验证的用户,并允许用户访问该页面。
很明显,根据我刚描述的方法,身份验证用户是一种非常不安全的方法。实际上,服务器将使用用户提供的密码生成某种哈希,并使用服务器上的某个加密库验证此哈希。
虽然高级概念是有效的,但它使我们能够了解Cookie在身份验证方面的价值。在我们继续本文的余下部分之前,请记住这个例子。
‘’’
很多像- ��e宿���(�KaTeX parse error: Undefined control sequence: \e at position 7: [����4\̲e̲�'这样的乱码- ��e宿��…[����4\e�’。
现在先忽略JWT的签名部分。无法将其解码为有意义的JSON对象的原因是,它比头部和负载更复杂。我们将很快进一步探讨这一点。让我们先看看头部和负载的内容。
头部具有类似于“alg”和“typ”的属性。因为它们代表解释这个混乱签名的“指令”,所以它们都在JWT中。
负载是最简单的部分,只是关于我们正在验证身份的用户的信息。
-sub-是“subject”的缩写,通常表示数据库中的用户ID。
-name-只是关于用户的一些任意元数据。
-admin-关于用户的一些其他任意元数据。
-iat-是“issued at”的缩写,表示此JWT的签发时间。
对于JWTs,您还可能在负载中看到以下信息:
-exp-是“expiration time”的缩写,表示此JWT过期的时间。
-iss-是“issuer”的缩写,在一个中央登录服务器发放许多JWT令牌时经常使用(在OAuth协议中也大量使用)
您可以在以下链接中查看JWT规范的所有“标准声明” at this link.
点击查看如何逐步生成签名。下面是伪代码示例:
‘’’