在 MEAN 栈中实现 SPA 身份验证

17 Mar 2025 | 5 分钟阅读

在上一节中,我们了解了 SPA 身份验证。现在,在本节中,我们将在项目中实现 SPA 身份验证。

我们的目标是创建一个这样的令牌,并在用户成功登录后将其返回给用户。我们将使用以下步骤将 SPA 身份验证添加到我们的项目或应用程序中。

1) 我们将回到 service.js 文件,在这里,我们需要创建一个合适的路由。到目前为止,我们只有一个允许我们创建新用户的路由。在 routes 文件夹的 user.js 文件中,我们将在这里使用 router 创建另一个路由。它也是一个 post 路由,但我们将监听 /login 或实际上是 /api/user/login,正如我们所知。在那里,我们有一个带有请求、响应和 next 的方法,当请求到达该路由时,我们将做一些事情。


Implementing SPA Authentication in MEAN Stack

2) 在该方法中,我们将创建一个这样的令牌。但首先,我们要验证凭据是否有效。这将是一个多步骤的过程

a. 我们需要找出电子邮件地址是否存在,为此,我们将使用我们的用户模型,并在数据库中找到该模型的一个实例。我们将传递一个对象来缩小我们想要找到的结果范围,并在数据库中的电子邮件地址与我们请求中附加的电子邮件地址匹配的用户。


Implementing SPA Authentication in MEAN Stack

b. 现在,我们将使用 then 块将其链接起来,以处理我们收到响应的情况,这将是用户,如果我们有用户,那么我们知道我们有该电子邮件地址。另一方面,如果我们找不到用户,我们就会得到 no user。现在,我们将检查用户是否存在。如果用户不存在,我们将返回一个响应,将状态码设置为 404,因为我们没有找到用户,或者 401,因为身份验证被拒绝,并根据需要发送一些 JSON 数据。


Implementing SPA Authentication in MEAN Stack

c. 我们将在那里返回一个响应,而且我们之前没有这样做,因为我们在 "if" 语句之后添加了代码。我们可以将其包装到一个 else 语句中,但这将意味着另一层嵌套,我们只想检查一下,如果用户不存在,则返回一个响应,否则继续。我们知道我们找到了具有该电子邮件地址的用户,这使我们可以比较用户在登录表单中输入的密码与数据库中存储的密码。

数据库中存储的密码的问题是我们使用 bcrypt 对其进行了哈希处理,并且我们知道可以使用密码破解这样的哈希。但是,如果我们有相同的输入,我们将始终获得相同的哈希。因此,我们将使用 bcrypt 的一个有用的函数,称为 compare,即比较函数,用于将输入与加密值进行比较,bcrypt 将告诉我们该输入是否会产生相同的值,而无需解密加密值,这是不可能的。

在此函数中,我们将传递来自请求的密码和用户的密码,如下所示

我们实际上将返回一个新的 promise,因此我们只需返回该比较操作的结果即可。


Implementing SPA Authentication in MEAN Stack

d. 我们链接另一个 then 块,在那里我们将返回该比较操作的结果。如果比较成功,则结果将为 true,如果失败,则结果将为 false。此外,我们可能会遇到其他错误,因此我们还添加了一个 catch 块。在这里,我们还返回我们的失败消息。在 then 块中,我们将检查结果是否为 true。如果返回 false,那么我们也会返回我们的响应,我们在其中拒绝身份验证,如下所示


Implementing SPA Authentication in MEAN Stack

e. 现在,如果我们有密码,我们将继续并创建 JSON Web Token。如果您想了解有关 JSON Web Tokens 的更多信息,您可以访问 io,在那里您可以阅读更多信息,并且您还可以看到此类令牌的外观。我们不需要从头开始构建它,有一个包可以帮助我们实现这一点,该包是 "jsonwebtoken"。我们将使用以下命令安装此包


Implementing SPA Authentication in MEAN Stack

这是一个第三方包,可帮助我们创建和验证此类令牌。

f. 然后,我们将在 bcrypt 下面导入此包,并在登录路由中使用它来创建新的 Web 令牌。在 then 块中,我们知道用户发送了有效的密码,我们将通过运行 jsonwebtokensign 方法来创建一个新令牌。此方法将根据我们选择的一些数据创建一个新令牌。


Implementing SPA Authentication in MEAN Stack

g. 现在,我们选择的输入数据将是一个 javascript 对象,我们在其中存储用户的电子邮件,而不是密码,因为我们不想将数据发回给用户,即使它被加密了。我们还想发回 ID,因为这稍后将在前端中很有用,如下所示


Implementing SPA Authentication in MEAN Stack

h. 现在,我们必须传递第二个参数。除了有效负载之外,我们还需要输入我们自己的密钥,例如我们用于创建这些哈希的密码。这只会存储在服务器上,并将用于验证这些哈希。这就是使它们无法破解的原因。通常,这应该是一个很长的单词或一个很长的字符串组合。


Implementing SPA Authentication in MEAN Stack

i. 我们还将传递另一个参数,该参数允许我们配置该令牌。它将是一个 javascript 对象,我们在其中设置了几个值。其中之一是 expireIn 属性,它允许我们定义这应该持续多长时间。如果我们想查看所有可用选项,我们可以使用 "Intellisense", 我们可以在其中看到可以设置的值,并且我们可以深入研究该软件包的官方文档。expireIn 接受一个字符串来描述持续时间,我们输入类似 1h 的内容,这是一个很好的值。该令牌不应持续太长时间,因为我们无法伪造它。当然,它存储在前端。


Implementing SPA Authentication in MEAN Stack

如果我们的应用程序不容易受到跨站点脚本攻击,它应该在那里是安全的,但它仍然是一种额外的安全机制,以确保它不会永远持续下去。

有了这些,我们就得到了一个一小时后过期的令牌,现在,我们正在创建该令牌。现在,让我们将其发送回前端,并将前端连接到此后端,以便在下一讲中连接到此新路由。