最近在开发一个统一认证服务,涉及到 OIDC
协议,其中授权码模式所颁发的 id_token 使用的是 JWT(JSON Web Token)。
因为这次使用的库的默认签名算法和以往不同,所以特地去翻阅了 JWT 的 RFC 文档( RFC 75191),一番阅读后对 JWT 的核心内容有了更深入的理解。
你也可以在 Authing 官网上了解 JWT Token 的释义及使用,以及在 Authing 系统中验证 token 的方法。什么是 JWT Token?
每次提到无状态的 JWT 时,相信都会看到另一种基于 Session 的用户认证方案介绍,这里也不例外,Session 的认证流程通常会像这样:
JWT 正好可以解决这些问题:
JWT 的用户认证流程颇具优势,将需要使用到的用户数据等信息放入 JWT 中,每次请求都会自动携带,只要保证密钥不泄露,JWT 就无法伪造。
一个简单的 JWT 示例如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOiIyMDIxLTEwLTI0IDAwOjAwOjAwIiwibmFtZSI6InRvZ2V0dG95b3UifQ.XdF46NflSUjnt-adAc6rNZEXI1OD6nxtwGuhz9qkxUA
把上面的 JWT 复制粘贴到 jwt.io2 这个网站中。
可以看出 JWT 以不同颜色区分,通过两个小数点隔开,分为了三部分:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret,
)
到这里,大多数人对 JWT 的认知应该是停留在此了,日常使用也已经足够,但你想更深入了解 JWT 的话,那你就得知道 JOSE 。
什么是 JOSE,它和 JWT 之间又有什么关系呢?
JOSE 全称 JSON Object Signing and Encryption ( RFC 71653, RFC 75204 ),它定义了一系列的标准,用来规范网络传输过程中使用 JSON 的方式,我们上面一直说的 JWT 其实是 JOSE 体系之一。
其中 JWT 又可分为 JWS 和 JWE 两种不同的实现,我们大部分日常所使用的,所说的 JWT 其实应该属于 JWS。为什么这么说,请看下文。
JWA 的全称是 JSON Web Algorithms (RFC 75185) ,字如其名,JOSE 体系中涉及到的所有算法就是它来定义的,比如通用算法有 Base64-URL 和 SHA,签名算法有 HMAC,RSA 和 Elliptic Curve(EC 椭圆曲线)。
本文不会深入到算法原理,只是想让你知道 JWA 是做什么的。上述 JWT 例子中的第一部分 Header 有个 alg 属性,其值是 HS256,也就是 HMAC+SHA256 算法。
JWS 的全称是 JSON Web Signature (RFC 75156) ,它的核心就是签名,保证数据未被篡改,而检查签名的过程就叫做验证。更通俗的理解,就是对应前面提到的 JWT 的第三部分 Signature,所以我才会说我们日常所使用的 JWT 都是 JWS。
通常在客户端-服务端模式中,JWS 使用 JWA 提供的 HS256 算法加上一个密钥即可,这种方式严格依赖密钥,但在分布式场景,可能多个服务都需要验证 JWT,若要在每个服务里面都保存密钥,那么安全性将会大打折扣,要知道,密钥一旦泄露,任何人都可以随意伪造 JWT。
解决办法就是使用非对称加密算法 RSA,RSA 有两把钥匙,一把公钥,一把私钥,可以使用私钥签发(签名分发)JWT ,使用公钥验证 JWT,公钥是所有人都可以获取到的。这样一来,就只有认证服务保存着私钥,进行签发,其他服务只能验证。
如下是一个使用 RS256 (RSA+SHA256) 算法生成的 JWT:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjF6aXlIVG15M184MDRDOU1jUENHVERmYWJCNThBNENlZG9Wa3VweXdVeU0ifQ.eyJqdGkiOiIzWUJ5eWZ2TDB4b01QNXdwTXVsZ0wiLCJzdWIiOiI2MDE5NDI5NjgwMWRjN2JjMmExYjI3MzUiLCJpYXQiOjE2MTI0NDQ4NzEsImV4cCI6MTYxMzY1NDQ3MSwic2NvcGUiOiJvcGVuaWQgZW1haWwgbWVzc2FnZSIsImlzcyI6Imh0dHBzOi8vc3RlYW0tdGFsay5hdXRoaW5nLmNuL29pZGMiLCJhdWQiOiI2MDE5M2M2MTBmOTExN2U3Y2IwNDkxNTkifQ.cYyZ6buwAjp7DzrYQEhvz5rvUBhkv_s8xzuv2JHgzYx0jbqqsWrA_-gufLTFGmNkZkZwPnF6ktjvPHFT-1iJfWGRruOOMV9QKPhk0S5L2eedtbKJU6XIEkl3F9KbOFwYM53v3E7_VC8RBj5IKqEY0qd4mW36C9VbS695wZlvMYnmXhIopYsd5c83i39fLBF8vEBZE1Rq6tqTQTbHAasR2eUz1LnOqxNp2NNkV2dzlcNIksSDbEGjTNkWceeTWBRtFMi_o9EWaHExdm5574jQ-ei5zE4L7x-zfp9iAe8neuAgTsqXOa6RJswhyn53cW4DwWg_g26lHJZXQvv_RHZRlQ
把它复制到 jwt.io 上面看看
注意我绿色框选中的地方,里面是一段 JSON,我们把它删掉,看看输入框的提示信息。
这里提示了,里面是填写公钥格式(通常为 PEM)或者 JWK(我们说过 RSA 算法是使用私钥签发 JWT,公钥进行验证),刚刚我们删掉的是一段 JSON,所以必然不是公钥格式,那是 JWK 吗?
当然是,JWK 的全称是 JSON Web Key (RFC 75177) ,它就是一个 JSON,JWK 就是用 JSON 来表示密钥(JSON 字段因密钥类型而异)。例如刚才删除的 JWK:
{
"e": "AQAB",
"kty": "RSA",
"n": "wVKQLBUqOBiay2dkn9TlbfuaF40_edIKUmdLq6OlvzEMrP4IDzdOk50TMO0nfjJ6v5830_5x0vRg5bzZQeKpHniR0sw7qyoSI6n2eSkSnFt7P-N8gv2KWnwzVs_h9FDdeLOeVOU8k_qzkph3_tmBV7ZZG-4_DEvgvat6ifEC-WzzYqofsIrTiTT7ZFxTqid1q6zrrsmyU2DQH3WdgFiOJVVlN2D0BuZu5X7pGZup_RcWzt_9T6tQsGeU1juSuuUk_9_FVDXNNCTObfKCTKXqjW95ZgAI_xVrMeQC5nXlMh6VEaXfO83oy1j36wUoVUrUnkANhp-dnjTdvJgwN82dGQ"
}
其中 kty 字段是必须的,代表密钥类型,支持 EC 椭圆曲线密钥,RSA 密钥和 oct 对称密钥。
JWK 和公钥格式 Pem 是可以互相转换的:
我们现在已经知道,验证这个 JWT 是需要公钥或 JWK 的,那你会不会好奇 jwt.io 这个网站是怎么知道 JWK 的呢,为什么一粘贴,就自动将 JWK 填充进去了。
原理其实很简单,而且已经是一种大家都遵循的规范了,就是将 JWK 放在 iss/.well-known/jwks.json
下,其中 iss 就是 Payload 里面的 iss。
当你在 jwt.io 粘贴下 JWT 的瞬间,jwt.io 会先解析 Header,判断出 JWT 使用的算法(JWA),接着解析出 Payload 的信息,由于这里是 RS256 算法, 所以还会去请求 Payload 里的 iss 下的 .well-known/jwks.json
得到 JWK,从而完成 JWS 的验证。
我们说过,经过 Signature 签名后的 JWT 就是指的 JWS,而 JWS 仅仅是对前两部分签名,保证无法篡改,但是其 Payload(载荷)信息是暴露的(只是作了 base64UrlEncode 处理)。因此,使用 JWS 方式的 Payload 是不适合传递敏感数据的,JWT 的另一种实现 JWE 就是来解决这个问题的。
JWE 全称是 JSON Web Encryption ( RFC 75168) ,JWS 的 Payload 是 Base64Url 的明文,而 JWE 的数据则是经过加密的,它可以使 JWT 更加安全。
JWE 提供了两种方案:共享密钥方案和公钥/私钥方案。
共享密钥方案的工作原理是让各方都知道一个密钥,大家都可以签名验证,这和 JWS 是一致的。
而公钥/私钥方案的工作方式就不同了,在 JWS 中私钥对令牌进行签名,持有公钥的各方只能验证这些令牌;但在 JWE 中,持有私钥的一方是唯一可以解密令牌的一方,公钥持有者可以引入或交换新数据然后重新加密,因此,当使用公钥/私钥方案时,JWS 和 JWE 是互补的。
想要理解这一点的更简单的方法是从生产者和消费者的角度进行思考。
生产者对数据进行签名或加密,消费者可以对其进行验证或解密。对于 JWS,私钥对 JWT 进行签名,公钥用于验证,也就是生产者持有私钥,消费者持有公钥,数据流动只能从私钥持有者到公钥持有者。
相比之下,对于 JWE,公钥是用于加密数据,而私钥用来解密,在这种情况下,数据流动只能从公钥持有者到私钥持有者。如下图所示(来源 JWT Handbook9):
相比于 JWS 的三个部分,JWE 有五个部分组成(四个小数点隔开)。一个 JWE 示例如下:
eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.
UGhIOguC7IuEvf*NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7Zx0-kFm1NJn8LE9XShH59*
i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgNZ\_\_deLKxGHZ7PcHALUzoOegEI-8E66jX2E4zyJKxYxzZIItRzC5hlRirb6Y5Cl_p-ko3YvkkysZIFNPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8Otv
zlV7elprCbuPhcCdZ6XDP0_F8rkXds2vE4X-ncOIM8hAYHHi29NX0mcKiRaD0-D-ljQTPcFPgwCp6X-nZZd9OHBv-B3oWh2TbqmScqXMR4gp_A.
AxY8DCtDaGlsbGljb3RoZQ.
KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY.
9hH0vgRfYgPnAHOd8stkvw
这五个部分的生成,也就是 JWE 的加密过程可以分为 7 个步骤:
base64(header) + '.' +
base64(encryptedKey) + '.' + // Steps 2 and 3
base64(initializationVector) + '.' + // Step 4
base64(ciphertext) + '.' + // Step 6
base64(authenticationTag) // Step 6
JWE 相比 JWS 更加安全可靠,但是不够轻量,有点复杂。
今天为大家讲了一些 JWT 不为人知的秘密,总结一下涉及到的知识点:
最后,再次附上 JOSE 的体系图,相关的 RFC 均备注在图上了:
RFC 7519 ↩︎
jwt.io ↩︎
RFC 7165 ↩︎
RFC 7520 ↩︎
RFC 7518 ↩︎
RFC 7515 ↩︎
RFC 7517 ↩︎
RFC 7516 ↩︎
JWT Handbook ↩︎