理解基于令牌(token)的认证

基于令牌的身份验证更简单。令牌有几种实现方式,但 JSON Web 令牌 (JWT) 是最常见的一种。基于令牌的身份验证是无状态的。这意味着服务器端不持久化任何会话,因为状态存储在客户端的令牌内。服务器的责任仅仅是使用密钥创建 JWT 并将其发送给客户端。客户端将 JWT 存储在本地存储或客户端 Cookie 中,并在每次发出请求时将其包含在标头中。然后,服务器验证 JWT 并发送响应。

但是,什么是 JWT?它是如何工作的?让我们在下一节中找到答案。

什么是 JSON Web 令牌?

要理解 JWT 的工作原理,我们首先应该理解它是什么。简而言之,JWT 是一个哈希 JSON 对象字符串,由标头、载荷和签名组成。JWT 按照以下格式生成:

header.payload.signature

标头通常由两部分组成:类型type)和 算法algorithm)。类型是 JWT,算法可以是 HMACSHA256RSA,这是一种使用密钥对令牌进行签名的哈希算法,例如:

{
  "typ": "JWT",
  "alg": "HS256"
}

载荷是 JWT 中存储信息(或声明)的部分,例如:

{
  "userId": "b08f86af-35da-48f2-8fab-cef3904660bd",
  "name": "Jane Doe"
}

在这个例子中,我们在载荷中只包含了两个声明。你可以根据需要放入任意数量的声明。你包含的声明越多,JWT 的大小就越大,这可能会影响性能。还有其他可选的声明,例如 iss(签发者)、sub(主题)和 exp(过期时间)。

如果你想了解更多关于 JWT 标准字段的详细信息,请访问 https://tools.ietf.org/html/rfc7519。

签名是使用编码后的标头、编码后的载荷、一个密钥以及标头中指定的算法计算出来的。无论你在标头部分选择哪种算法,你都必须使用该算法来加密 JWT 的前两部分:base64(header) + '.' + base64(payload),例如,在下面的伪代码中:

// 签名算法
data = base64urlEncode(header) + '.' + base64urlEncode(payload)
hashedData = hash(data, secret)
signature = base64urlEncode(hashedData)

签名是 JWT 中唯一不公开可读的部分,因为它使用密钥进行加密。除非有人拥有该密钥,否则他们无法解密此信息。因此,上述伪代码的示例输出是三个由点分隔的 Base64-URL 字符串,可以轻松地在 HTTP 请求中传递:

// JWT 令牌
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJiMDhmODZhZi0zNWRhLTQ4Zj
ItOGZhYi1jZWYzOTA0NjYwYmQifQ.-xN_h82PHVTCMA9vdoHrcZxH-x5mb11y1537t3rGzcM

让我们在下一节通过一个示例流程来看看这种令牌身份验证是如何工作的。

令牌认证流程

基于令牌的身份验证可以通过以下示例身份验证流程来理解:

  1. 用户从其浏览器上的客户端应用向服务器发送其凭据,例如用户名和密码。

  2. 如果凭据正确,服务器会检查用户名和密码,并返回一个签名的令牌(JWT)。

  3. 此令牌存储在客户端。它可以存储在本地存储、会话存储或 Cookie 中。

  4. 客户端应用通常将此令牌作为附加标头包含在后续对服务器的任何请求中。

  5. 服务器接收并解码 JWT,如果令牌有效,则允许请求访问。

  6. 当用户注销时,客户端会销毁令牌,并且不再需要与服务器进行进一步交互。

在基于令牌的身份验证中,通常你不应该在载荷中包含任何敏感信息,并且令牌不应长期保存。用于包含令牌的附加标头应采用以下格式:

Authorization: Bearer <token>

基于令牌的身份验证的可伸缩性不是问题,因为令牌存储在客户端。跨域共享也不是问题,因为 JWT 是一个包含所有必要信息的字符串,包含在请求标头中,服务器会对客户端发出的每个请求进行检查。在 Node.js 应用中,我们可以使用其中一个 Node.js 模块,例如 jsonwebtoken,来为我们生成令牌。让我们在下一节中看看如何使用这个 Node.js 模块。

使用 Node.js 模块处理 JWT

正如我们之前提到的,jsonwebtoken 可用于在服务器端生成 JWT。你可以在以下简化的步骤中同步或异步地使用此模块:

  1. 通过 npm 安装 jsonwebtoken

    $ npm i jsonwebtoken
  2. 在服务器端导入并签署令牌:

    import jwt from 'jsonwebtoken'
    const token = jwt.sign({ name: 'john' }, 'secret', { expiresIn: '1h' })
  3. 异步验证来自客户端的令牌:

    try {
      const verified = jwt.verify(token, 'secret')
    } catch(err) {
      // 处理错误
    }

    如果你想了解更多关于此模块的信息,请访问 https://github.com/brianloveswords/node-jws。

因此,你现在对基于会话和基于令牌的身份验证有了基本的了解,我们将指导你如何在同时使用 KoaNuxt 的服务器端和客户端应用中应用它们。在本章中,我们将使用基于令牌的身份验证在我们的应用中创建两个身份验证选项:本地身份验证和 Google OAuth 身份验证。本地身份验证是我们应用内部和本地验证用户的选项,而 Google OAuth 身份验证是我们使用 Google OAuth 验证用户的选项。因此,让我们在接下来的章节中找到答案!