从一个 REST API 迁移到 GraphQL:实现身份验证和授权

阅读时长 14 分钟读完

从一个 REST API 迁移到 GraphQL:实现身份验证和授权

REST API 一直是前端开发人员的常用方式之一。它可以提供简单且直接的数据,支持多种编程语言和平台,并且可以使用 HTTP 请求进行访问。然而,REST API 的局限性逐渐凸显出来,而 GraphQL 随之崭露头角,成为新的后端数据交互方式。

REST API 因其基于资源本质,对于扁平化的数据结构处理非常方便,而对于复杂数据甚至需要对不同的接口进行调用来完成数据的获取和处理,而 GraphQL 拥有更加优秀的数据查询策略,可以以高效和精细的方式获取和操作数据。

GraphQL 能够通过定义 Schema 和 Resolver 来将数据彻底解耦,需要什么数据直接利用 GraphQL 定义的 Schema 即可从 Resolver 层面获取到。在 GraphQL 中,身份验证和授权是重要的内容之一。本文将从一个 REST API 迁移到 GraphQL,并详细介绍如何在 GraphQL 中实现身份验证和授权。

迁移准备

在开始迁移之前,我们需要先准备好我们的参考架构。本文中,我们将使用 jwt 和 passport 实现身份验证,使用权限控制来进行 API 授权。假设我们已经有一个 REST API,其中具有以下几个接口:

为了将 API 迁移到 GraphQL,我们首先需要编写一个新的 Schema。Schema 是定义我们的 API 的核心,它描述了数据应该如何查询和变化。我们首先定义了一个 User 的 Schema 如下:

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

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

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

我们的新 Schema 包含三个部分:

  • User:定义了一个 User 类型,包含了用户的各个信息。
  • Query:定义了查询操作,包括获取用户列表和用户详情。
  • Mutation:定义可变操作,包括创建、更新和删除用户。

身份验证

接下来,我们需要确保我们的用户进行身份验证。我们将使用 jsonwebtoken 进行客户端的身份验证,同时在服务器端使用 passport 进行身份验证和授权。

客户端身份验证

我们可以在客户端本地存储 JSON Web Token(JWT),以验证用户是否已登录。JWT 由服务端签发,包含一些元数据以及有效负载。最常见的有效负载包括用户 ID、过期时间等等。此外,JWT 还由 Signature 组成,Signature 用于验证该 Token 是否由我们所信任的服务端签发。

当用户进行登录操作时,服务端将生成一个 JWT 并返回给客户端,客户端存储此 Token,并使用该 Token 在每个 HTTP 请求中向服务端提供该 Token。服务端使用自己的密钥对这个 Token 进行验证,以确保它未被篡改,并使用其中的数据来验证用户。

服务端身份验证

接下来我们将看到如何在服务端使用策略模式进行身份验证和授权。我们将使用 passport,它是一个流行的 Node.js 用户验证库。它的核心思想是将鉴权策略与细节分离开,以便我们可以轻松地重用相同的鉴权策略,而不必考虑细节。

我们将首先编写一个基本的 LocalStrategy,该策略从请求中提取出用户名和密码,并检查它们是否与数据库中保存的用户名和密码匹配。如果匹配,则通过回调将用户对象返回给 Passport。

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

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

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

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

在这段代码中,我们首先从请求中提取出用户名和密码,从数据库中查找与该用户名匹配的用户。如果匹配则我们将用户传递给通过回调返回。请注意,我们使用了 async/await 语法来处理数据库查询。

接下来,我们需要为每个 GraphQL 请求启用身份验证。由于 GraphQL 不使用 HTTP 内置身份验证机制,我们必须从头开始实现它。这意味着我们必须使用不同的 HTTP 头来传递 JWT,或使用不同的 query 参数。本文中我们将使用 HTTP 头进行身份验证。

我们需要在 GraphQL server 的 context 中添加我们的身份验证逻辑,以便我们可以在执行每个查询时检查用户是否已经登录。我们创建一个 Express 中间件,并从请求头部中提取并验证 JWT。然后,我们将拥有一个经过身份验证的用户对象,我们可以将其传递到所有 GraphQL 请求中。

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

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

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

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

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

在上面的代码中,我们通过 Express 中间件添加了 auth(req, res, next) 方法。该方法将在请求开始时进行身份验证,如果验证成功,一个已认证用户的对象将会被添加到请求中,并将传递到所有的 GraphQL 解析器中。

需要注意的是,上面的身份验证方法必须放在所有 GraphQL 中间件之前执行,以确保在所有查询中都可以使用用户对象。

授权

我们已经能够成功地验证用户的身份。现在,在用户身份验证完成之后,我们需要确保他只能访问他所拥有的资源,对于其他资源,我们需要拒绝他的访问请求。我们通过定义一个基于角色的访问控制列表(ACL)来实现这一点。

访问控制列表

我们将用户分为两类:管理员和普通用户。管理员可以访问所有资源,而普通用户只可以访问具有特定权限的资源。我们定义一个访问控制列表(ACL),其中存储了对于各个资源的访问允许规则。

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

在这个 ACL 中,我们可以看到一个 user 对象和一个 post 对象。每个对象都有一个访问控制列表,其中存储了管理和用户的特定动作的允许用户角色。例如,在此 ACL 中,只有管理员可以创建新的 post,但是管理员和普通用户都可以读取和更新 post。对于 user,管理员和普通用户都可以更新,但只有管理员可以读取。

AuthorizationHeader

为了执行此 ACL,我们需要使用 AuthenticationHeader 的身份验证方法,读取所有 GraphQL 请求,并检查当前用户是否具有请求所需的角色。

我们定义一个新的 authorization GraphQL 类型,该类型表示 GraphQL 查询的某些部分需要特定的角色。

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

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

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

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

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

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

在上述代码中,我们首先定义了 AuthorizationRole 枚举型,表示可用的用户角色。之后我们定义一个 Directive,它将声明所需的角色。然后,我们使用这个 Directive 来指示哪些操作需要具有角色验证。在此示例中,我们仅仅定义了 createPostdeletePost 权限仅管理者使用。

接下来,我们通过 GraphQL 中间件添加一个新的 auth 方法。该方法将从 authorization directive 中获取所需的角色,与当前用户的角色进行比较,并拒绝访问。

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

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

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

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

  ------
-

在上面的代码中,我们读取 authorization directive 以获取所需的角色,并检查用户是否拥有访问该操作所需的角色。如果用户没有提供适当的授权,则我们将抛出一个 AuthenticationError

现在,GraphQL server 完成了用户身份验证和角色授权。对于所有 GraphQL 请求都将使用身份验证逻辑进行身份验证,并且只有授权用户才能访问具有特定权限的操作。在迁移过程的最后一步中,我们需要将我们的旧 REST API 转化为 GraphQL 查询。

GraphQL 查询

在我们将 REST API 转移到 GraphQL 查询之前,我们定义我们的对象之间的关系。我们的查询应该返回一个 User 对象,该对象具有 idnameemail 属性。我们将在每个用户对象上返回后一个 boolean 类型属性,该属性表示该用户是否可以执行特定的操作,例如 deleteUser

接下来,我们将在每个查询上使用角色授权逻辑,以确保用户只能访问他们被授权的查询。

这意味着只有管理员才能访问 users 接口,而普通用户只能访问自己的 user,以及其他查询和操作。同样地,我们将角色授权逻辑添加到所有可变操作(即 Mutation):

最后,我们定义解析器,将我们的旧 REST API 转换为 GraphQL 查询。对于此示例中的每个 REST API 动作,我们将编写一个等效的 GraphQL 解析器函数。假设 resolver 的路径为 /users/:id,我们的 GraphQL 解析器将如下所示:

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

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

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

在上述代码中,我们导入了获取用户和更新方法,查找 ID 来的用户,并将其传递给客户端。解析器现在通过 user 对象进行身份验证,并且仅当用户 ID 与 URL 中的 ID 相同时才允许访问,否则将抛出错误。这种方式,我们的 GraphQL server 将使用前面所述的逻辑来执行身份验证和授权,并将所有解析逻辑转换为 GraphQL 查询以执行。

结论

在本文中,我们展示了如何从一个 REST API 迁移到 GraphQL,包括如何实现身份验证和授权。我们从一个定义基本 Schema 开始,然后添加身份验证逻辑和角色授权。最后,我们转换了我们的所有 REST API 解析器函数,并将它们转化为 GraphQL 查询。

GraphQL 提供了强大的查询和变化策略,可以轻松处理复杂的数据结构。此外,GraphQL 还提供了更多灵活性,可以在其中执行身份验证和访问控制。在实际中使用 GraphQL,可以大大简化前端开发模式,提升开发效率和体验。

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

纠错
反馈