基于 Hapi 的 JSON Web Token 认证实践

阅读时长 17 分钟读完

在现代 Web 应用中,认证是必不可少的一环。JSON Web Token(JWT)是一种基于 JSON 的轻量级身份验证和授权机制,被广泛应用于前后端分离的 Web 应用中。Hapi 是一个 Node.js 的 Web 框架,提供了可扩展、可配置和可测试的接口,使得使用 JWT 认证在 Hapi 中变得异常简单。

本文将介绍如何在 Hapi 中使用 JWT 认证。我们将通过一个示例应用程序来演示如何实现 JWT 认证,以及如何在 Hapi 中进行配置和使用。

什么是 JWT?

JWT 是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间作为 JSON 对象安全地传输信息。JWT 可以使用 HMAC 算法或 RSA 公钥/私钥对进行签名,以验证发送方是否为其声称的发送方,并保证消息的完整性。

JWT 由三部分组成:头部、载荷和签名。头部包含了令牌的类型和所使用的算法信息。载荷包含了需要传输的信息,例如用户 ID、角色等。签名是由头部和载荷组成的字符串进行签名生成的,用于验证令牌的完整性。

在 Hapi 中实现 JWT 认证

在 Hapi 中使用 JWT 认证,需要安装 hapi-auth-jwt2 插件。该插件提供了一组策略和路由配置,用于实现 JWT 认证。安装该插件的命令如下:

安装完成后,在 Hapi 的配置文件中注册该插件:

-- -------------------- ---- -------
----- ---- - ----------------------
----- --- - ---------------------
----- ------- - --------------------------

----- ---- - ----- -- -- -
  ----- ------ - -------------
    ----- -----
    ----- ------------
  ---

  ----- -----------------
    ----
    --------
  ---

  -- ---
-

-------

接下来,我们需要定义一个 JWT 策略,以及相应的路由配置。在 Hapi 中,可以使用 server.auth.strategy() 方法定义策略。以下是一个简单的 JWT 策略定义:

在上面的代码中,我们定义了一个名为 jwt 的策略,使用了 jwt 插件。其中,key 参数为签名密钥,validate 参数为用户验证函数,verifyOptions 参数为签名算法。用户验证函数的返回值应该是一个包含 isValid 属性的对象,用于表示用户是否验证成功。

接下来,我们需要定义需要进行 JWT 认证的路由。在 Hapi 中,可以使用 auth 参数指定需要使用的策略。以下是一个简单的 JWT 认证路由配置:

-- -------------------- ---- -------
--------------
  ------- ------
  ----- -------------
  -------- --------- -- -- -
    -- ------
    ------ ------- --------
  --
  -------- -
    ----- ------ -- -- --- ------
  --
---

在上面的代码中,我们定义了一个名为 /api/users 的路由,使用了 jwt 策略进行认证。当用户访问该路由时,如果未提供有效的 JWT,则返回 401 错误。

示例应用程序

为了更好地理解 JWT 认证在 Hapi 中的实现方法,我们将通过一个示例应用程序来演示如何实现 JWT 认证。该应用程序包含以下功能:

  • 用户注册和登录
  • JWT 认证
  • 获取用户信息

准备工作

首先,我们需要创建一个新的 Hapi 项目,并安装所需的依赖:

在上面的命令中,我们安装了 Hapi、Joi、bcrypt、hapi-auth-jwt2 和 Mongoose 库。其中,Joi 用于验证用户输入数据的有效性,bcrypt 用于密码加密,Mongoose 用于与 MongoDB 数据库进行交互。

接下来,我们需要创建一个 MongoDB 数据库,并创建一个名为 users 的集合。

用户注册和登录

在开始实现 JWT 认证之前,我们需要实现用户注册和登录功能。以下是一个简单的用户模型定义:

-- -------------------- ---- -------
----- -------- - --------------------

----- ---------- - --- -----------------
  ------ -
    ----- -------
    --------- -----
    ------- -----
    ---------- -----
  --
  --------- -
    ----- -------
    --------- -----
  --
  ----- -
    ----- -------
    --------- -----
  --
  ----- -
    ----- -------
    ----- -------- ---------
    -------- -------
  --
---

-------------- - ---------------------- ------------

在上面的代码中,我们定义了一个名为 User 的模型,包含了用户的电子邮件、密码、姓名和角色信息。在用户注册时,我们需要将密码进行加密。以下是一个简单的用户注册路由定义:

-- -------------------- ---- -------
----- --- - ---------------------
----- ------ - ------------------
----- ---- - -------------------------

-- ------
--------------
  ------- -------
  ----- ----------------
  -------- ----- --------- -- -- -
    ----- - ------ --------- ---- - - ----------------

    -- ------------
    ----- ------ - ------------
      ------ --------------------------------
      --------- -------------------------------
      ----- ------------------------
    ---
    ----- - ----- - - ----------------- ------ --------- ---- ---
    -- ------- -
      ----- -----------------------------------------------
    -

    -- ----------
    ----- ---- - ----- -------------- ----- ---
    -- ------ -
      ----- ---------------- ------- -------------------
    -

    -- -----
    ----- -------------- - ----- --------------------- ----
    ----- ------- - --- ------
      ------
      --------- ---------------
      -----
    ---
    ----- ---------------

    ------ ---------------- ---------- -------------------------
  --
---

在上面的代码中,我们定义了一个名为 /api/register 的路由,用于注册新用户。在用户注册时,我们首先使用 Joi 库验证用户输入数据的有效性。然后,我们检查用户是否已经注册。最后,我们将密码进行加密,创建新用户,并将其保存到数据库中。

用户登录功能与用户注册类似。以下是一个简单的用户登录路由定义:

-- -------------------- ---- -------
-- ------
--------------
  ------- -------
  ----- -------------
  -------- ----- --------- -- -- -
    ----- - ------ -------- - - ----------------

    -- ------------
    ----- ------ - ------------
      ------ --------------------------------
      --------- ------------------------
    ---
    ----- - ----- - - ----------------- ------ -------- ---
    -- ------- -
      ----- -----------------------------------------------
    -

    -- --------
    ----- ---- - ----- -------------- ----- ---
    -- ------- -
      ----- ------------------- ----- -- ---------------------
    -

    -- ------
    ----- --------------- - ----- ------------------------ ---------------
    -- ------------------ -
      ----- ------------------- ----- -- ---------------------
    -

    -- -- ---
    ----- ----- - -----------------
      ---- ---------
      ----- ----------
      ----- ----------
    ---

    ------ - ----- --
  --
---

在上面的代码中,我们定义了一个名为 /api/login 的路由,用于用户登录。在用户登录时,我们首先使用 Joi 库验证用户输入数据的有效性。然后,我们检查用户是否存在,并验证用户密码。最后,我们使用 server.jwt.sign() 方法创建 JWT,并将其返回给客户端。

在用户登录成功后,客户端应该将 JWT 存储在本地存储或会话存储中。在每次向服务器发送请求时,客户端应该将 JWT 添加到请求头中。以下是一个简单的客户端请求示例:

JWT 认证

现在,我们已经实现了用户注册和登录功能。接下来,我们需要使用 JWT 认证保护需要授权访问的路由。在 Hapi 中,我们可以使用 server.auth.strategy() 方法定义 JWT 策略。以下是一个简单的 JWT 策略定义:

-- -------------------- ---- -------
--------------------------- ------ -
  ---- ---------------- -- ----
  --------- ----- --------- -------- -- -- -
    -- --------
    ----- ---- - ----- ---------------------------
    -- ------- -
      ------ - -------- ----- --
    -

    -- ------
    ------ - -------- ----- ------------ ---- --
  --
  -------------- - ----------- --------- -- -- ----
---

在上面的代码中,我们定义了一个名为 jwt 的策略,使用了 jwt 插件。其中,key 参数为签名密钥,validate 参数为用户验证函数,verifyOptions 参数为签名算法。在用户验证函数中,我们使用 JWT 中的 sub 字段查找用户,并将其返回给客户端。

接下来,我们需要使用 auth 参数指定需要使用的策略。以下是一个简单的受保护路由定义:

-- -------------------- ---- -------
-- ------
--------------
  ------- ------
  ----- -------------
  -------- --------- -- -- -
    ----- ---- - -------------------------
    ------ ------- ---------------
  --
  -------- -
    ----- ------ -- -- --- ------
  --
---

在上面的代码中,我们定义了一个名为 /api/users 的受保护路由,使用了 jwt 策略进行认证。当用户访问该路由时,如果未提供有效的 JWT,则返回 401 错误。如果 JWT 有效,则返回用户信息。

完整示例代码

以下是完整的示例代码:

-- -------------------- ---- -------
----- ---- - ----------------------
----- --- - ---------------------
----- ------ - ------------------
----- -------- - --------------------
----- --- - ---------------------
----- ------- - --------------------------
----- ---- - -------------------------

----- ---- - ----- -- -- -
  ----- -------------------------------------------------------- -
    ---------------- -----
    ------------------- -----
    --------------- -----
  ---

  ----- ------ - -------------
    ----- -----
    ----- ------------
  ---

  ----- -----------------
    ----
    --------
  ---

  -- ------
  --------------
    ------- -------
    ----- ----------------
    -------- ----- --------- -- -- -
      ----- - ------ --------- ---- - - ----------------

      -- ------------
      ----- ------ - ------------
        ------ --------------------------------
        --------- -------------------------------
        ----- ------------------------
      ---
      ----- - ----- - - ----------------- ------ --------- ---- ---
      -- ------- -
        ----- -----------------------------------------------
      -

      -- ----------
      ----- ---- - ----- -------------- ----- ---
      -- ------ -
        ----- ---------------- ------- -------------------
      -

      -- -----
      ----- -------------- - ----- --------------------- ----
      ----- ------- - --- ------
        ------
        --------- ---------------
        -----
      ---
      ----- ---------------

      ------ ---------------- ---------- -------------------------
    --
  ---

  -- ------
  --------------
    ------- -------
    ----- -------------
    -------- ----- --------- -- -- -
      ----- - ------ -------- - - ----------------

      -- ------------
      ----- ------ - ------------
        ------ --------------------------------
        --------- ------------------------
      ---
      ----- - ----- - - ----------------- ------ -------- ---
      -- ------- -
        ----- -----------------------------------------------
      -

      -- --------
      ----- ---- - ----- -------------- ----- ---
      -- ------- -
        ----- ------------------- ----- -- ---------------------
      -

      -- ------
      ----- --------------- - ----- ------------------------ ---------------
      -- ------------------ -
        ----- ------------------- ----- -- ---------------------
      -

      -- -- ---
      ----- ----- - -----------------
        ---- ---------
        ----- ----------
        ----- ----------
      ---

      ------ - ----- --
    --
  ---

  -- ------
  --------------
    ------- ------
    ----- -------------
    -------- --------- -- -- -
      ----- ---- - -------------------------
      ------ ------- ---------------
    --
    -------- -
      ----- ------ -- -- --- ------
    --
  ---

  -- --- ----
  --------------------------- ------ -
    ---- ---------------- -- ----
    --------- ----- --------- -------- -- -- -
      -- --------
      ----- ---- - ----- ---------------------------
      -- ------- -
        ------ - -------- ----- --
      -

      -- ------
      ------ - -------- ----- ------------ ---- --
    --
    -------------- - ----------- --------- -- -- ----
  ---

  ----- ---------------
  ------------------- ------- -- ---- -----------------
--

-------------------------------- ----- -- -
  -----------------
  ----------------
---

-------

总结

在本文中,我们介绍了如何在 Hapi 中使用 JWT 认证。我们通过一个示例应用程序演示了如何实现用户注册和登录功能,以及如何使用 JWT 认证保护需要授权访问的路由。JWT 认证是一种非常流行的身份验证和授权机制,在前后端分离的 Web 应用中被广泛应用。Hapi 是一个灵活、可扩展和可测试的 Node.js Web 框架,使得使用 JWT 认证在 Hapi 中变得异常简单。

来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/650d3f3395b1f8cacd6f4d63

纠错
反馈