JWT 简介(还有 JWS、JWE、JWA、JWK)

在过去的几年里,用户数据的安全性和隐私性一直是人们日益关注的问题。同时,JWT 作为对抗它的一种技术,也被越来越多地使用。

在过去的几年里,用户数据的安全性和隐私性一直是人们日益关注的问题。同时,JWT 作为对抗它的一种技术,也被越来越多地使用。了解 JWT 将使您比其他软件工程师更有优势。JWT 乍一看可能很简单,但很难理解。

在本文中,我们将主要探讨 JWT 和 JWS。此外,我们还将快速浏览 JWE、JWA 和 JWK。本文旨在让读者理解 JWT 的概念,而无需深入探讨该主题。

注意:如果你有兴趣学习如何使用 Java Spring Boot 实现 JWT,可以访问我的另一篇文章:

这些是什么?

在深入了解之前,我们最好知道 JWT、JWS、JWE、JWA 和 JWK 之间的联系。

从上图中,我们可以看出:

  • JSON Web Token (JWT)是一种抽象,以JSON Web Signature (JWS)JSON Web Encryption (JWE)的形式表示。
  • JSON Web 签名 (JWS)JSON Web 加密 (JWE)使用JSON Web 算法 (JWA)中定义的签名和加密算法作为保护自身的一种方式。
  • JSON Web 算法 (JWA)中定义的签名算法的公钥可以作为JSON Web Key (JWK)托管。

现在我们知道了它们之间的关系,让我们更深入地了解一下 JWT、JWS、JWE、JWA 和 JWK。

JSON 网络令牌 (JWT)

首先,让我们看看RFC 7519中定义的 JWT 的定义。

JSON Web Token (JWT) 是一种紧凑的声明表示格式
,适用于空间受限的环境,例如 HTTP
授权标头和 URI 查询参数。JWT 将声明编码
为 JSON 对象,该对象用作
JSON Web 签名 (JWS) 结构的有效负载或
JSON Web 加密 (JWE) 结构的明文,从而
使声明能够进行数字签名或完整性保护消息验证码(
MAC) 和/或加密。JWT 始终
使用 JWS 紧凑序列化或 JWE 紧凑
序列化来表示。

从文本中,我们可以理解 JWT 不是一个结构,而是一组 JWS 或 JWE 形式的声明,作为其保护自身的方式。在最基本的形式中,JWS 和 JWE 的区别在于每个人都可以看到 JWS 的有效负载,而 JWE 是加密的。


在本文中,我们将探讨更多关于 JWS 而非 JWE 的内容。

JSON 网络算法 (JWA)

JWA ( RFC 7518 ) 代表 JSON Web Algorithm,是一种规范,定义了创建 JWT 的散列和加密算法。

例如,以下是我们可以用来创建具有 JWS 结构的 JWT 的哈希算法。

JSON 网络密钥 (JWK)

JWK ( RFC 7517 ) 代表 JSON Web 密钥。JWK 是一种 JSON 数据结构,其中包含有关散列函数的加密密钥的信息。这是一种以 JSON 格式存储散列密钥的方法。

{
    "kty":"EC",
    "crv":"P-256",
    "x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
    "y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",
    "kid":"Public key used in JWS spec Appendix A.3 example"
}

JWK 通常用于托管具有非对称密钥(私钥和公钥)的散列函数的公钥,因此消费者可以自己获取密钥。

JSON 网络签名 (JWS)

JWS ( RFC 7515 ),代表 JSON Web Signature,是 JWT 使用的结构之一。这是 JWT 最常见的实现。JWS 由 3 部分组成:JOSE 标头、有效负载和签名。

以下是紧凑序列化中的 JWS 示例。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

JWS 紧凑序列化示例

如果我们解码 JWS,我们将得到 JOSE(JavaScript 对象签名和加密)标头:

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

有效载荷:

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

下一部分是签名。我们不会解码它,因为它是一个字节值。

SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

与 JWT(在 JWS 结构中)相关的最常见问题是什么使它安全,因为每个人都可以解码 JWT 并查看其数据。JWT 是安全的,因为不是每个人都可以创建它,只有拥有密钥的人才能创建它。

JSON 网络加密 (JWE)

JWE ( RFC 7516 ) 与 JWS 不同,它使用加密算法对其内容进行加密。唯一能看到 JWT 内部内容的是有密钥的那个。

JWE紧凑序列化的结构如下:

BASE64URL(UTF8(JWE Protected Header)) || ’.’ ||
BASE64URL(JWE Encrypted Key) || ’.’ ||
BASE64URL(JWE Initialization Vector) || ’.’ ||
BASE64URL(JWE Ciphertext) || ’.’ ||
BASE64URL(JWE Authentication Tag)

让我们看一个例子:

eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.RD09fEltrYPVNoGt2KY1Odv_5eDxkU4VX1f__P8b9zl9uzh5bmvvJy35dL-hYlUib1g63qnWBEfeSyDk5cAIQiMt6PZCBQzuWQJQlQtuo2UPLZznmLPqah37uHKB4a57q_lWf_W9soyZbO7Zj7QRNz4ZR4s5ozRHArSZcc1pAL-pYuHKyeh6Ey8t4bk66wkthjjfOjXvIfOlgbemhibegmE4GpQL6F-m0teqcAE-OxkaBRTmmb4AD5HdrCJWCIIuC52fzuWrhcoNmHM74ggtWUUjlHaKpwcVE-IWINTFaz5Pi9u4U3vnVNOZwDwB0TLSQvqnPwTZ-bYWNj8vH4TS_w.Pjo5QK1u1otxgcuBR7e8ew._OElhHugS2L6Kp04HhbFt6dLij_KXhO654RmT4JKyswYBX0wqRWt7ZzAE6eCHfJSJdMQYxqVSNloGb4OSIzYcTEo174lBZBINkHW-w2K6E0.QBDgBFizm80HLVkZvfBPCg

该示例使用RSA_OAEP_256密钥管理算法并AES_128_CBC_HMAC_SHA_256作为其加密算法。如果我们解密 JWE,我们将得到:

{
  "iss": "https://codecurated.com",
  "exp": 1651417524,
  "iat": 1651417224
}

解码 JWT

现在让我们使用以下示例更深入地了解 JWT:

eyJhbGciOiJIUzI1NiIsImtpZCI6IjIwMjItMDUtMDEifQ.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkJyaWxpYW4gRmlyZGF1cyIsImlhdCI6MTY1MTQyMjM2NX0.qsg3HKPxM96PeeXl-sMrao00yOh1T0yQfZa-BsrtjHI

示例中的 JWT 是具有 JWS 紧凑序列化结构的 JWT。我们需要用.(点)分割 JWT,然后 Base64 对它们进行解码以查看其中的内容。

在对 JWT 进行拆分和解码之后,我们会得到三个部分:

  • JOSE(JavaScript 对象签名和加密)标头
  • 有效载荷
  • 签名

何塞标题

eyJhbGciOiJIUzI1NiIsImtpZCI6IjIwMjItMDUtMDEifQ

对字符串进行 Base64 解码后,我们将得到:

{
  "alg": "HS256",
  "kid": "2022-05-01"
}

JWT 的这一部分称为 JOSE 标头。通过 JOSE 标头,JWT 可以通知客户端如何处理 JWT。


让我们分解一下 JWT 中的两个字段:

  • alg:包含有关 JWT 签名算法的信息。
  • kid:包含用于验证 JWT 的密钥的 id 信息。我们将在 JWK 部分对此进行更多探讨。

alg是唯一的强制性标头,也是大多数情况下唯一需要的标头,但您可以在此处查看更多标头。

有效载荷

排除了 JOSE 标头,让我们看一下第二部分,即有效负载。

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkJyaWxpYW4gRmlyZGF1cyIsImlhdCI6MTY1MTQyMjM2NX0

我们解码后:

{
  "sub": "1234567890",
  "name": "Brilian Firdaus",
  "iat": 1651422365
}

JWT 的这一部分称为有效负载。有些字段带有术语 JWT 声明 ( subnameiat)。所以,现在似乎是谈论 JWT 声明的好时机。

JWT 声明分为三种类型:

  • 注册索赔
  • 公开索赔
  • 私人索赔

让我们从注册索赔开始,一一分解。注册声明是最初在 RFC 7519 中记录的声明。

  • iss(Issuer):表示谁是 JWT 的发行人。
  • sub(Subject):表示请求JWT的用户id。
  • aud(观众):显示谁是 JWT 的预期消费者。
  • exp(Expiration):JWT 的过期时间。

您可以在此处查看完整列表。正如 RFC 7519 中所规定的,这些声明都不是强制性的,但它们对于保护您的 JWT 至关重要。

我们将探讨的下一种索赔类型是私人索赔。这些声明可以是任何东西。由 JWT 创建者或消费者决定声明名称和功能。在指定私有声明时,您需要注意不要在名称中引起任何冲突。

最后一种是公开声明,这是一种通过 IETF 公开注册的声明。您可以在此处查看公开声明列表。

大多数人没有任何用例需要他们注册他们的声明。为获得最佳实践,您可以搜索公共声明列表并使用适合您用例的声明。

签名

如果您已经来到这部分,您可能想知道是什么让 JWT 安全,因为每个人都可以看到它的内容。好吧,JWS 的有效负载是供所有人阅读的。使 JWT 安全的是消费者可以验证谁是发布 JWT 的人。

JWT 中的签名部分是使用散列函数创建的。如果您不熟悉哈希函数,它是一种将一个对象映射到另一个对象的算法。哈希函数有两个关键特性,使其适合保护 JWT:

  • 它只能以一种方式工作
  • 哈希过程的结果总是相同的

在本文中,我们将探讨 JWT 中最常用的 2 个哈希函数:

  • HMAC SHA-256
  • ECDSA256

现在,让我们分解示例中使用的签名:

qsg3HKPxM96PeeXl-sMrao00yOh1T0yQfZa-BsrtjHI

这是使用HS256带有密钥的 MAC 算法生成的(在 base64 中):

7TgIAQCcYUA27bCI5+m7InRwp/mzQ+ArnFW/4c0Q51U=

MAC 算法接收字节值作为其HS256密钥参数并产生字节值作为其输出。因此,与标头和有效负载不同,如果我们尝试解码签名,我们将获得字节值。

让我们更彻底地探索数字签名或 MAC 算法。

HMAC SHA-256

我们将探讨的第一个算法是 HMAC SHA-256 ( HS256),这是一种使用带有对称密钥的散列函数的 MAC 算法。具有对称密钥的散列函数意味着散列函数只有一个密钥。因此,JWT 的生产者和消费者将使用相同的密钥对 JWT 进行签名和验证。使用这种算法的优点是它不需要很多 CPU 资源来创建散列。为 hs256 密钥推荐的最小字节长度为 32 字节。必须使用加密安全的伪随机数生成器生成密钥,以确保其随机性。

ECDSA-256

ECDSA-256 ( ES256) 与 HMAC 不同,是一种使用带有非对称密钥的散列函数的算法。使用非对称密钥的散列函数意味着我们需要生成两个密钥。一个密钥称为私钥,可用于签署和验证 JWT 签名。另一个密钥称为公钥,只能用于验证 JWT 签名。

顾名思义,公钥可以存储在公共空间(通常作为 JWK)中,因此需要验证您的 JWT 签名的客户端可以快速获取它。相反,必须保护私钥并将其视为凭证。

选择哪个散列函数?

我们已经学习了两种算法,HS256ES256,但是什么时候选择一种而不是另一种呢?您可以通过考虑 JWT 的生产者和消费者是否不是同一个组件来轻松做出决定。
如果 JWT 的消费者是同一个组件,那么您可以使用该HS256算法。这个散列函数最常见的用例是当您使用 JWT 创建身份验证系统时。

另一方面,如果 JWT 由不同的组件生成和使用,则可以使用ES256. 这样,您可以保护您的密钥并确保没有其他人(甚至是公钥所有者)可以代表您创建 JWT。有些人认为 HS256 是一种反模式,因为据说 JWT 是用来提高安全性的,但大多数使用这种算法的用例都会降低安全性。例如,假设您计划将 JWT 用作身份验证会话而不是数据库。在这种情况下,您的系统更加不安全,因为您无法使 JWT 过期,因此您无法启动会话。

将公钥存储为 JWK(JSON Web 密钥集)

如果您打算让公众使用和验证您的 JWT,建议将公钥作为 JWK 托管在 URL 上。这样,如果消费者想要验证您的 JWT,他们可以查询托管 JWK 的特定 URL 并获取公钥。

{
  "keys": [
    {
      "kty": "EC",
      "kid": "2022-05-01",
      "x": "g_pYyqY7Htj8Aa989Ura0_mwRdqJPEnhknKzaUrztj8",
      "y": "MwOFYLE-VYre92hU0iDjNx36dk7cX6xdGgdgLIPt6Ts",
      "crv": "P-256"
    },
    {
      "kty": "EC",
      "kid": "2020-01-01",
      "x": "6bw04ZlSMjxVzC7gXv75XAposOVTONh45ZPR0AeYaoU",
      "y": "vYyCSIt0m5k4Q5A_uW8h3nEYJvgA8PgREErLcaiAHgQ",
      "crv": "P-256"
    }
  ]
}

您可能已经注意到 JSON 中有不止一个键,这就是它被称为 JSON Web Key Set的原因。如果您的产品使用多个密钥,您可以在 JWK 中托管每个密钥。要确定要使用的密钥,您需要kid在 JOSE 标头中添加或密钥 id 字段。

{
    "alg":"ES256",
    "kid":"2022-05-01"
}

这样,客户端将通过将kidJOSE 标头 JWT 中的 与 JWK 中的进行比较来知道要获取哪个密钥。其他字段组合起来将成为公钥。

带走

在本文中,我们了解到:

  1. JWT 是一个关于如何允许一个或多个参与方安全地交换信息的抽象概念。JWT 的实现以 JWS 或 JWE 的形式出现。
  2. JWS 和 JWE 的区别在于 JWS 允许每个人看到它的有效载荷,而 JWE 不允许它使用加密方法。
  3. 即使每个人都可以看到其有效负载,JWS 仍被认为是安全的原因在于,JWS 的创建者可以使用 MAC 或签名验证算法通过其签名进行验证。这样,消费者可以确定创建 JWT 的人是预期的人。
  4. 我们已经探索了两种类型的算法,HS256并且ES256HS256适用于 JWT 的生产者和消费者是同一个组件时,ES256适用于生产者和消费者是不同组件的情况。
  5. 我们可以使用 JSON Web Key Set 来托管带有非对称密钥的散列函数的公钥。您需要kid在 JWT 的 JOSE 标头中设置该字段,以便消费者可以将其与 JWK 中的字段进行比较以获得兼容的密钥。

唉,我要感谢你阅读到最后。您可以采取的下一步是学习如何实现它(如何在 Java Spring Boot 中实现 JWT)。您可以在此处查看许多流行编程语言的库。