JWT(JSON Web Token)通常用于标识经过身份验证的用户。 它们由身份验证服务器签发,并由客户端-服务器(用于保护其API)使用。
如果您想了解有关JSON Web Token(JWT)的详细信息,您来对地方了。
我们将涵盖以下内容:
- JWT是什么?
- JWT的结构
- JWT声明约定
- 它们如何工作(通过示例)
- JWT的优缺点
- 开发过程中常见的问题
- 进一步的阅读材料
JWT是什么?
JSON Web令牌是一种开放的行业标准,用于在两个实体之间共享信息, 通常是客户端(如您的应用的前端)和服务器(您的应用的后端)。
它们包含需要 共享的信息的JSON对象。
每个JWT还使用加密技术(哈希)签名,以确保JSON内容(也称为JWT声明)不能被客户端或恶意方篡改。
例如,当您使用Google登录时,Google会发出一个包含以下声明JSON负载的JWT:
{
"iss": "https://accounts.google.com",
"azp": "1234987819200.apps.googleusercontent.com",
"aud": "1234987819200.apps.googleusercontent.com",
"sub": "10769150350006150715113082367",
"at_hash": "HK6E_P6Dh8Y93mRNtsDB1Q",
"email": "jsmith@example.com",
"email_verified": "true",
"iat": 1353601026,
"exp": 1353604926,
"nonce": "0394852-3190485-2490358",
"hd": "example.com"
}
通过上述信息,使用Google登录的客户端应用可以准确知道最终用户是谁。
什么是令牌以及为什么需要它?
您可能会想,为什么身份验证服务器不能直接发送一个纯JSON对象,而需要将其转换为“令牌”。
如果身份验证服务器将其作为纯JSON发送,客户端应用的API将无法验证他们接收到的内容是否正确。 例如,恶意攻击者可能会更改用户ID(上述JSON中的sub声明),应用程序的API将无法知道这种情况的发生。
由于这个安全问题,身份验证服务器需要以一种可以被客户端应用验证的方式传输此信息,这就是“令牌”概念的由来。
简单地说,令牌是一个包含某些信息的字符串,可以安全地验证。
这可以是指向数据库中ID的一组随机字母数字字符,或者可以是客户端可以自行验证的编码JSON(称为JWT)。
JWT的结构
一个JWT包含三个部分:
- Header(头部):包含两部分信息:
- 使用的签名算法。
- 令牌类型,在这种情况下,通常是“JWT”。
- Payload(负载):包含声明或JSON对象。
- Signature(签名):通过加密算法生成的字符串,可用于验证JSON负载的完整性。
JWT声明约定
您可能已经注意到,在上面Google签发的JWT示例中,JSON负载具有非显而易见的字段名。它们使用sub
、iat
、aud
等:
- iss:令牌的签发者(在此例 中为Google)
- azp和aud:Google为您的应用分配的客户端ID。这样,Google知道哪个网站在尝试使用其登录服务,网站知道JWT是专门为它们签发的。
- sub:终端用户的Google用户ID
- at_hash:访问令牌的哈希值。OAuth访问令牌不同于JWT,因为它是一个不透明的令牌。访问令牌的目的是客户端应用可以向Google查询有关登录用户的更多信息。
- email:终端用户的电子邮件ID
- email_verified:用户是否已验证其电子邮件。
- iat:JWT创建的时间(自纪元以来的毫秒数)。
- exp:JWT将过期的时间(自纪元以来的毫秒数)。
- nonce:客户端应用可以用来防止重放攻击。
- hd:用户的托管G Suite域
使用这些特殊的键名是为了遵循JWT中重要字段名称的行业惯例。遵循这一惯例,使得不同语言的客户端库能够检查由任何身份验证服务器签发的JWT的有效性。例如,如果客户端库需要检查JWT是否已过期,它只需查找iat字段。
它们如何工作(通过示例)
解释JWT如何工作最简单的方法是通过一个示例。
我们将从为特定JSON负载创建JWT开始,然后进行验证:
-
创建JSON 我们采用以下最小的JSON负载:
{
"userId": "abcd123",
"expiry": 1646635611301
} -
创建JWT签名密钥并决定签名算法 首先,我们需要一个签名密钥和一个要使用的算法。我们可以使用任何安全随机源生成签名密钥。为了本文的目的,我们使用:
- 签名密钥:
NTNv7j0TuYARvmNMmWXo6fKvM4o6nv/aUi9ryX38ZH+L1bkrnD1ObOQ8JAUmHCBq7Iy7otZcyAagBLHVKvvYaIpmMuxmARQ97jUVG16Jkpkp1wXOPsrF9zwew6TpczyHkHgX5EuLg2MeBuiT/qJACs1J0apruOOJCg/gOtkjB4c=
- 签名算法:
HMAC + SHA256
,也称为HS256
。
- 签名密钥:
-
创建Header 这包含有关使用哪个签名算法的信息。与负载类似,这也是一个JSON,并将附加到JWT的开头(因此称为
Header
):{
"typ": "JWT",
"alg": "HS256"
} -
创建签名 首先,我们删除负载JSON中的所有空格,然后对其进行base64编码,得到
eyJ1c2VySWQiOiJhYmNkMTIzIiwiZXhwaXJ5IjoxNjQ2NjM1NjExMzAxfQ
。您可以尝试将此字符串粘贴到在线base64解码器中以检索我们的JSON。同样,我们删除Header JSON中的空格并对其进行base64编码,得到
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
。我们将这两个base 64字符串连接起来,中间用
.
分隔,如<header>.<payload>
,得到eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJhYmNkMTIzIiwiZXhwaXJ5IjoxNjQ2NjM1NjExMzAxfQ
。这样做没有特别原因,只是为了设置一个行业可以遵循的惯例。现在,我们对上述连接字符串和密钥运行
Base64 + HMACSHA256
函数,得到签名:Base64URLSafe(
HMACSHA256("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJhYmNkMTIzIiwiZXhwaXJ5IjoxNjQ2NjM1NjExMzAxfQ", "NTNv7j0TuYARvmNMmWXo6fKvM4o6nv/aUi9ryX38ZH+L1bkrnD1ObOQ8JAUmHCBq7Iy7otZcyAagBLHVKvvYaIpmMuxmARQ97jUVG16Jkpkp1wXOPsrF9zwew6TpczyHkHgX5EuLg2MeBuiT/qJACs1J0apruOOJCg/gOtkjB4c=")
)结果为:
3Thp81rDFrKXr3WrY1MyMnNK8kKoZBX9lg-JwFznR-M
我们仅出于行业惯例对其进行base64编码。 -
创建JWT 最后,我们附加生成的签名,如
<header>.<body>.<signature>
,创建我们的JWT:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJhYmNkMTIzIiwiZXhwaXJ5IjoxNjQ2NjM1NjExMzAxfQ.3Thp81rDFrKXr3WrY1MyMnNK8kKoZBX9lg-JwFznR-M
-
验证JWT 身份验证服务器将JWT发送回客户端前端。前端将JWT附加到对客户端API层的网络请求中。API层将执行以下步骤来验证JWT:
- 获取JWT的
Header
部分(eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
)。 - 对其进行base64解码以获得纯文本JSON:
{"typ":"JWT","alg":"HS256"}
- 验证
typ
字段的值是否为JWT
和alg
是否为HS256
。如果不是,将拒绝JWT。 - 获取签名密钥并在传入的
JWT
的Header
和Body
- 获取JWT的