JWT、JWS、JWE、JWA、JWK简介及使用方法

3,070 阅读12分钟

JWT简介(也包括JWS、JWE、JWA、JWK)

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

在这篇文章中,我们将主要探讨JWT和JWS。此外,我们还将快速浏览JWE、JWA和JWK。本文旨在让读者了解JWT的概念,而不需要太过深入地探讨这个话题。

它们是什么?

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

  • [JSON] (dzone.com/articles/un…) 网络令牌(JWT) 是一个抽象的,以JSON网络签名(JWS) JSON网络加密(JWE) 的形式表示。
  • JSON Web签名(JWS)JSON Web加密(JWE) 使用JWA中定义的签名和加密算法。 JSON网络算法 (JWA) 中定义的签名和加密算法作为保证自身安全的方式。
  • 中定义的签名算法的公钥可以被托管为 JSON Web密钥(JWK)

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

JSON Web令牌(JWT)

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

"JSON Web令牌(JWT)是一种紧凑的索赔表示格式,旨在用于空间受限的环境,如HTTP授权头URI查询参数。JWT将索赔编码为JSON对象,作为JSON Web签名(JWS)结构的有效载荷或作为JSON Web加密(JWE)结构的明文来传输,使索赔能够被数字签名或用消息验证码(MAC)和/或加密的完整性保护。JWT总是使用JWS Compact Serialization或JWE Compact Serialization来表示"。

从这段文字中,我们可以理解JWT不是一种结构,而是一组以JWS或JWE为形式的主张,作为其自我保护的方式。在最基本的形式上,JWS和JWE之间的区别是,每个人都可以看到JWS的有效载荷,而JWE的有效载荷是加密的。

在本文中,我们将对JWS比JWE进行更多的探讨。

JSON网络算法(JWA)

JWA(RFC 7518)是JSON Web算法的缩写,它是一种规范,定义了制作JWT的散列和加密算法。

例如,以下是我们可以用来创建一个具有JWS结构的JWT的散列算法

JSON Web Key (JWK)

JWK(RFC 7517)代表JSON Web Key。JWK是一个JSON数据结构,包含了关于散列函数的加密密钥的信息。它是一种以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签名的缩写,是JWT使用的结构之一。它是JWT的最常见的实现。JWS由3部分组成:JOSE头、有效载荷和签名。

下面是一个JWS的紧凑序列化的例子。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

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

JSON

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

有效载荷:JSON

JSON

{
  "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进行解密,我们将得到。

JSON

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

解码JWT

现在让我们通过下面的例子来深入了解一下JWT。

eyJhbGciOiJIUzI1NiIsImtpZCI6IjIwMjItMDUtMDEifQ.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkJyaWxpYW4gRmlyZGF1cyIsImlhdCI6MTY1MTQyMjM2NX0.qsg3HKPxM96PeeXl-sMrao00yOh1T0yQfZa-BsrtjHI

例子中的JWT是一个具有JWS紧凑序列化结构的JWT。我们需要通过. (点)来分割JWT,并对它们进行Base64解码以查看里面的内容。

在对JWT进行分割和解码后,我们将得到三个部分。

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

JOSE头

eyJhbGciOiJIUzI1NiIsImtpZCI6IjIwMjItMDUtMDEifQ

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

JSON

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

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

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

  • alg:包含JWT的签名算法信息。
  • kid:包含用于验证JWT的密钥的ID信息。我们将在JWK部分探讨更多关于这个问题。

alg 是唯一的强制性头,也是大多数情况下唯一需要的头,但还有很多头。

有效载荷

说完了JOSE头,让我们来看看第二部分,即有效载荷。

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkJyaWxpYW4gRmlyZGF1cyIsImlhdCI6MTY1MTQyMjM2NX0

在我们对它进行解码之后:

JSON

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

JWT的这一部分被称为有效载荷。有一些字段有JWT的说法(sub,name,iat)。因此,现在似乎是一个谈论JWT索赔的好时机。

有三种类型的JWT索赔:

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

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

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

你可以在这里查看完整的列表。正如RFC 7519规定的那样,这些要求都不是强制性的,但它们对于保证你的JWT的安全至关重要。

我们要探讨的下一种索赔类型是私人索赔。这些要求可以是任何东西。由JWT的创建者或消费者来决定索赔的名称和功能。

警告: 当指定私人索赔时,你需要注意不要在名称上造成任何碰撞。

最后一种是公共索赔,这是一种通过IETF公开注册的索赔。

大多数人没有任何用例需要他们注册他们的权利要求。对于最佳实践,你可以搜索公共权利要求的列表,并使用适合你的用例的那一个。

签名

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

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

  • 它只以一种方式工作
  • 散列过程的结果总是相同的

在这篇文章中,我们将探讨JWT中最常用的两个哈希函数:

  • HMAC SHA-256
  • ECDSA256

现在,让我们拆开例子中使用的签名。

qsg3HKPxM96PeeXl-sMrao00yOh1T0yQfZa-BsrtjHI

它是使用HS256 MAC算法和秘密密钥(base64格式)生成的:

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

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

让我们更深入地探讨数字签名或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过期,所以你不能踢出会话。

将公钥存储为JWKs(JSON网络密钥集)

如果你打算让公众消费并验证你的JWT,建议将公钥作为JWKs托管在一个URL上。这样,如果消费者想验证你的JWT,他们可以查询托管JWKs的特定URL并获得公钥。

JSON

{
  "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网络密钥。如果你的产品使用一个以上的密钥,你可以在JWKs中托管每个密钥。为了确定使用哪个密钥,你需要在你的JOSE头中添加kid 或密钥id字段。

JSON

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

这样,客户端就可以通过比较JOSE头JWT中的kid 和JWK中的 ,知道要获得哪个密钥。其他字段,结合起来,将构成公钥。

经验之谈

在这篇文章中,我们已经了解到:

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

唉,我想感谢你读到最后。你可以采取的下一步是学习如何实现它。