在过去的几年里,用户数据的安全性和隐私性一直是人们日益关注的问题。同时,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 声明 ( sub
, name
, iat
)。所以,现在似乎是谈论 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 签名的客户端可以快速获取它。相反,必须保护私钥并将其视为凭证。
选择哪个散列函数?
我们已经学习了两种算法,HS256
和ES256
,但是什么时候选择一种而不是另一种呢?您可以通过考虑 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"
}
这样,客户端将通过将kid
JOSE 标头 JWT 中的 与 JWK 中的进行比较来知道要获取哪个密钥。其他字段组合起来将成为公钥。
带走
在本文中,我们了解到:
- JWT 是一个关于如何允许一个或多个参与方安全地交换信息的抽象概念。JWT 的实现以 JWS 或 JWE 的形式出现。
- JWS 和 JWE 的区别在于 JWS 允许每个人看到它的有效载荷,而 JWE 不允许它使用加密方法。
- 即使每个人都可以看到其有效负载,JWS 仍被认为是安全的原因在于,JWS 的创建者可以使用 MAC 或签名验证算法通过其签名进行验证。这样,消费者可以确定创建 JWT 的人是预期的人。
- 我们已经探索了两种类型的算法,
HS256
并且ES256
.HS256
适用于 JWT 的生产者和消费者是同一个组件时,ES256
适用于生产者和消费者是不同组件的情况。 - 我们可以使用 JSON Web Key Set 来托管带有非对称密钥的散列函数的公钥。您需要
kid
在 JWT 的 JOSE 标头中设置该字段,以便消费者可以将其与 JWK 中的字段进行比较以获得兼容的密钥。
唉,我要感谢你阅读到最后。您可以采取的下一步是学习如何实现它(如何在 Java Spring Boot 中实现 JWT)。您可以在此处查看许多流行编程语言的库。