在本文中,将解释JSON Web令牌(JWT)的基本概念,以及使用它们的原因。JWT是确保应用程序中的信任和安全性的重要部分。JWT允许以安全的方式表示声明,例如用户数据。
为了解释JWT是如何工作的,让我们从一个抽象的定义开始。
JSON Web令牌(JWT)是一个JSON对象,它在 RFC 7519 中定义为在双方之间表示一组信息的安全方法。令牌由头、有效负载和签名组成。
简而言之,JWT就是一个字符串,格式如下:
header.payload.signature
应该注意,双引号字符串实际上被认为是一个有效的JSON对象。
为了展示JWT是如何以及为什么被实际使用的,我们将使用一个简单的3实体示例(参见下图)。本例中的实体是用户、应用程序服务器和身份验证服务器。身份验证服务器将向用户提供JWT。使用JWT,用户可以安全地与应用程序通信。
在本例中,用户首先使用身份验证服务器的登录系统(例如用户名和密码、Facebook登录、谷歌登录等)登录身份验证服务器。然后,身份验证服务器创建JWT并将其发送给用户。当用户对应用程序进行API调用时,用户将JWT与API调用一起传递。在这一步中,应用服务器将验证传入的由身份验证服务器创建的JWT(稍后将更详细地解释验证过程)。因此,当用户使用附加的JWT进行API调用时,应用程序可以使用JWT来验证API调用来自经过身份验证的用户。
现在,JWT本身,以及它是如何构建和验证的,将被更深入地研究。
步骤一 创建头
JWT的头组件包含关于如何计算JWT签名的信息。报头是一个JSON对象,格式如下:
{
"typ": "JWT",
"alg": "HS256"
}
在这个JSON中,“typ”键的值指定对象是JWT,而“alg”键的值指定使用哪种散列算法来创建JWT签名组件。在我们的示例中,我们使用HMAC-SHA256算法(一种使用密钥的散列算法)来计算签名(在步骤3中有更详细的讨论)。
步骤二 创建负载
JWT的有效负载组件是存储在JWT内部的数据(该数据也称为JWT的“声明”)。在我们的示例中,身份验证服务器创建一个JWT,其中存储有用户信息,特别是用户ID。
{
"userId": "b08f86af-35da-48f2-8fab-cef3904660bd"
}
在示例中,我们只向有效载荷提交了一个声明。你可以根据需要配置任意多的声明。JWT有效载荷有几种不同的标准声明,比如“iss”发布者、“sub”subject和“exp”过期时间。在创建JWT时,这些字段可能很有用,但是它们是可选的。有关JWT标准字段的更详细列表,请参阅JWT的wikipedia页面。
请记住,数据的大小将影响JWT的总体大小,这通常不是问题,但是过大的JWT可能会对性能产生负面影响并导致延迟。
步骤三 创建签名
使用以下伪代码计算签名:
// signature algorithm
data = base64urlEncode( header ) + “.” + base64urlEncode( payload )
hashedData = hash( data, secret )
signature = base64urlEncode( hashedData )
这个算法所做的是base64url对步骤1和步骤2中创建的头和有效负载进行编码。然后,算法用连接符(.)将运算后的编码字符串连接起来。在我们的伪代码中,这个连接的字符串被赋值给 data
。字符串data
使用JWT头中指定的哈希算法对密钥进行哈希。计算出的哈希数据被赋值给hashedData
。然后对这个散列数据进行base64url编码,以生成JWT签名。
在我们的例子中,头部和有效载荷都是base64url编码的:
// header
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
// payload
eyJ1c2VySWQiOiJiMDhmODZhZi0zNWRhLTQ4ZjItOGZhYi1jZWYzOTA0NjYwYmQifQ
然后,在连接的编码头和编码负载上应用带有密钥的指定签名算法,得到签名所需的散列数据。在我们的示例中,这意味着在数据字符串上应用HS256算法,将密钥设置为字符串“secret”,以获得hashedData
字符串。之后,通过base64url编码hashedData
字符串,得到如下JWT签名:
// signature
-xN_h82PHVTCMA9vdoHrcZxH-x5mb11y1537t3rGzcM
步骤四 将所有三个JWT组件放在一起
现在我们已经创建了所有三个组件,可以创建JWT了。记住JWT的header.payload.signature
结构,我们只需要将组件组合起来,并用句点(.)分隔它们。我们使用头部和有效负载的base64url编码版本,以及我们在步骤3中得到的签名。
// JWT Token
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJiMDhmODZhZi0zNWRhLTQ4ZjItOGZhYi1jZWYzOTA0NjYwYmQifQ.-xN_h82PHVTCMA9vdoHrcZxH-x5mb11y1537t3rGzcM
您可以在JWT.io上通过浏览器尝试创建自己的JWT。
回到我们的示例,身份验证服务器现在可以将此JWT发送给用户。
JWT如何保护我们的数据?
重要的是要理解使用JWT的目的不是以任何方式隐藏数据。使用JWT的原因是为了证明发送的数据实际上是由一个真实的源创建的。如上面的步骤所示,JWT中的数据是编码和签名的,而不是加密的。编码数据的目的是转换数据的结构。签名数据允许数据接收器验证数据源的真实性。因此,编码和签名数据并不保护数据。另一方面,加密的主要目的是保护数据和防止未经授权的访问。有关编码和加密之间的区别的更详细解释,以及关于哈希如何工作的更多信息,请参阅本文。
由于JWT只签名和编码,JWT不加密,因此JWT不保证敏感数据的任何安全性。
步骤五 验证JWT
在我们的简单3实体示例中,我们使用的JWT由HS256算法签名,其中只有身份验证服务器和应用服务器知道密钥。当应用程序设置其身份验证过程时,应用程序服务器从身份验证服务器接收密钥。由于应用程序知道密钥,所以当用户对应用程序进行JWT附加API调用时,应用程序可以执行与JWT上步骤3中相同的签名算法。然后,应用程序可以验证从自己的哈希操作获得的签名是否与JWT本身的签名匹配(即它与身份验证服务器创建的JWT签名匹配)。如果签名匹配,则意味着JWT是有效的,这表明API调用来自一个真实的源。否则,如果签名不匹配,则意味着接收到的JWT无效,这可能是对应用程序的潜在攻击的一个指示器。因此,通过验证JWT,应用程序在自己和用户之间添加了一层信任。
总结
我们讨论了JWT是什么,如何创建和验证它们,以及如何使用它们来确保应用程序及其用户之间的信任。这是理解JWT的基本原理以及它们为何有用的起点。JWT只是确保应用程序中的信任和安全性的一小部分。