自从我开始学习JSON网络令牌以来,我一直在想我们如何验证它们。
我知道我们对令牌进行签名,并使用签名的令牌来验证其真实性。但我还是很想知道更多,并想知道为什么我从未详细了解过这个过程。
我希望这篇文章能帮助你了解签署JWT的工作原理以及如何验证签署的令牌。
什么是JWT?
在我们开始之前,让我们快速回顾一下什么是JSON Web令牌。
JSON网络令牌(或JWT)是一种紧凑的、URL安全的方式,用于在双方之间传输数据。
它们由开放标准(RFC 7519)定义,由三个部分组成:一个头、一个有效载荷和一个一般的加密部分。
JWT在生成时是有签名的,在收到相同的签名JWT时要进行验证,以确保它在传输过程中没有被修改。
关于什么是JWT的更详细介绍,我建议你阅读我的博客:JSON网络令牌(JWT)以及我们为什么使用它们。
为什么你不需要知道签名和验证的实际工作原理?🤔
现在,为什么大多数JWT资源只是说 "然后你签名和验证",然后就不了了之了?嗯,答案是抽象化。
例如,当你驾驶一辆汽车时,你不需要知道发动机是如何工作的,也不需要亲自调校发动机以获得汽车的最佳性能。
相反,你相信制造商已经使用了他们的专业知识,并做了应有的努力来制造对你有用的东西。
同样地,你不需要知道签署和验证JWT的确切过程,以便有效地使用它们来验证和授权你的应用程序和API。
请注意,虽然你一般不应该自己签署和验证令牌,但了解它的工作原理可以帮助你在使用JWT时感到更舒服。但一般来说,身份提供商和身份即服务平台,如Auth0、Okta和微软活动目录,都能确保这个过程很简单。
因此,如果你仍然好奇(像我一样)想知道它是如何工作的,那么请继续阅读。
什么是JSON网络令牌?🤔
我在本教程中更详细地介绍了这一点,但让我们做一个简单的回顾。
JSON网络令牌由三个URL安全的字符串段组成,用句号连接起来.
第一个段是头段。它通常看起来像这样。
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9
头部段是一个JSON对象,包含一个签名算法和令牌类型。它是base64Url编码的。
解码后,它看起来像这样:
{
"alg": "RS256",
"typ": "JWT"
}
JWT的有效载荷段
第二段是有效载荷段。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0
这是一个包含数据要求的JSON对象,其中包含关于用户的信息和其他与认证有关的信息。
这是JWT从一个实体到另一个实体所传达的信息。它也是base64Url编码的。数据声明可能看起来像这样:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022
}
JWT的加密/签名段
最后一个部分是加密/签名部分。JWT是经过签名的,所以它们在传输过程中不能被修改。当授权服务器发出一个令牌时,它使用一个密钥来签署它。
当客户端收到ID令牌时,客户端也使用一个密钥来验证签名。
根据签署算法的不同,所使用的密钥也可能不同。如果使用的是非对称签名算法,你就会使用不同的密钥来签名和验证。在这种情况下,只有授权服务器能够签署令牌。
签署和验证JWT是如何进行的?🤔
如何签署JWT
在这篇文章中,我将使用RS256签名算法。RS256是一种带有SHA-256的RSA数字签名算法。
SHA-256是一种非对称密钥加密算法,它使用一对密钥:一个公钥和一个私钥来加密和解密。
在这里,授权服务器将使用私钥,而接收令牌以验证它的应用程序将使用公钥。
签名输入
首先,我们取JWT的前两段(头和有效载荷),它看起来像这样:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0
这基本上是base64url编码的头和有效载荷,用""连接:.`
base64UrlEncode(header) + "."
+ base64UrlEncode(payload)
而上面是签名输入。
对签名输入进行哈希处理
然后我们使用SHA-256散列算法对签名输入进行散列。散列将一个值转换为另一个值。散列函数使用一种数学算法,从一个现有的值中生成一个新的值。
注意:
- 散列是不可逆的。一旦我们对一个输入进行了散列,我们就不能再回到原来的输入。
- 对于相同的输入,散列将始终产生相同的输出。
- 没有两个不同的散列输入会产生相同的输出。
SHA-256 (
base64UrlEncode(header) + "."
+ base64UrlEncode(payload)
)
在这一点上,我们有一个头和有效载荷段的哈希值。我们可以将其与其他散列值进行比较,但我们不能反过来返回到原始的签名输入。
加密签名输入
接下来,我们对哈希值的签名输入进行加密。与散列不同,加密是可逆的。授权服务器使用私人加密密钥对散列的签名输入进行加密,以产生一个输出。
这个最终输出(散列的、加密的、编码的头和有效载荷)是JWT的加密/签名部分:
RSA (
SHA-256 (
base64UrlEncode(header) + "."
+ base64UrlEncode(payload)
),
{RSA Private Key}
)
你已经有了它。这就是JSON网络令牌的签名是如何产生的。
如何验证JWT
现在你知道了令牌是如何被签名的,我们可以继续了解收到令牌的人如何验证这个JWT包含没有被篡改的数据。
让我们来看看一个收到JWT并需要验证它的应用程序。该应用还可以访问授权服务器的公钥。
对JWT的验证是为了达到一个目的,即我们可以有效地比较我们收到的东西和我们期望的东西。
解码要求
应用程序可以解码头和有效载荷以获得一些信息。
请记住,这两个部分是base64Url编码的,以使它们的URL安全。这些段没有任何密码学上的安全。
你可以用一个简单的在线base64解码工具来做。一旦它们被解码,我们可以很容易地读取其中的信息。
例如,我们可以解码头段,看看JWT说它是用什么算法签署的。
从解码后的头部,我们可以看到:
{
"alg": "RS256",
"typ": "JWT"
}
当我们读取JWT头中的算法时,我们应该验证它是否与我们配置的预期相符。如果不匹配,我们应该直接拒绝该令牌。
哈希算法(再次)
如果令牌中的算法与我们期望的RS256相匹配,我们就知道我们需要生成头和有效载荷段的SHA-256哈希值。
记住,散列是不可逆的,但相同的输入将总是产生相同的输出。所以我们将对串联的、base64Url编码的头和有效载荷进行散列。现在我们有了在应用端计算的签名输入哈希值。
解密
散列的签名输入也在JWT的签名中,但它已经被授权服务器用私钥加密了。应用程序可以访问公钥,所以我们可以解密签名。
一旦这样做了,我们就可以获得原始的哈希值:授权服务器在首次生成令牌时生成的哈希值。
比较哈希值
现在我们可以将解密的哈希值与计算的哈希值进行比较。如果它们相同,那么我们就验证了JWT头和有效载荷段的数据在授权服务器创建令牌和我们的应用程序收到令牌之间没有被修改。
验证令牌声明
此外,一旦我们验证了签名,我们就可以验证JSON Web令牌的数据。有效载荷段中的声称也可以被验证,因为它们包含关于令牌发行者、令牌到期、令牌的预期受众、将令牌与授权请求绑定的信息等。
这些要求为应用程序提供了签名验证所不能提供的关于令牌的细节。
例如,对声明的检查可以发现,一个技术上有效的令牌实际上是为不同的应用程序或用户准备的,它已经过期了,它来自一个与该应用程序没有关系的发行者,等等。
总结
我们现在已经介绍了如何签署JWT和验证JWT签名。我希望这能帮助你更好地理解JWT,并与它们一起工作。我仍然想重申,你不应该自己签署和验证令牌。
有一些身份平台,如Auth0、Okta、Ping Identity等等,可以为你做这些事情。他们还提供SDK和库,用于应用程序或API方面的验证和令牌管理。
如果你对用Auth0保护你的应用程序感兴趣,你需要一个Auth0账户。如果你还没有,你可以注册一个免费的。
谢谢你的阅读!我真的希望你觉得这篇文章有用。我总是有兴趣知道你的想法,并乐意回答你心中可能存在的任何问题。如果你认为这篇文章很有用,请分享它,以帮助向其他人推广它。
谢谢你的阅读!:)