JWT是什么
- 2025-03-13 21:30:00
- 丁国栋
- 原创 70
在学习编程之前觉得jwt是啥玩意,好高深哦,擦,学了编程以后,尼玛,就这?
确实,jwt并不神秘,也不难。今天我想和大家分享一下我是如何认识jwt的。
先上官网:https://jwt.io/
好,正文开始了。
昨天下班时,看群消息有人在问为什么有个服务无法对接了,于是我去测试环境准备给找个截图说明可能是token不对或者被改过了。然而我竟然看到了令人发指的一幕,不知道哪个好小子把生产环境的服务对接到测试环境来了,这是严重的违规操作。一方面这个测试环境很多同事都在用,token有暴露的风险;另一方面这个token如果是真的,那么在测试环境的操作可能会影响到生产环境。所以,这个操作很令人无语,属于极其不规范操作,毫无安全责任意识。我一定得找出来这个小可爱来。
为了去验证这个token是否正确以及它属于谁,我去阅读了这个服务的源码,虽然它是GoLang写的,但照猫画虎依稀还是能看懂,原来这生成的是jwt token。本以为它是加密存在数据库里的,想通过代码看看它存在哪里了,只要找到它与账户的关系就知道它对应的账号是谁了,没想到它用的jwt token,一个不需要保存在数据库或者其他地方就能完成验证的token。于是去jwt官网看了一眼,好家伙,这个东西用户数据是透明的。用户数据是透明的怎么说?容我分析。
先上图:
这个图是截图自jwt官网,现在我们来看这个jwt token,很明显不过。它由三部分组成:头部.载荷.签名。例如官网显示这个:“eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30”
用点号分割成三部分:
- eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
- eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0
- KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30
- eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 --> base64_decode --> {"alg":"HS256","typ":"JWT"}
- eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0 --> base64_decode -->{"sub":"1234567890","name":"John Doe","admin":true,"iat":1516239022}
那么第三部分的签名是怎么来的?我继续看了下代码,原来它是用HMAC算法对头部载荷用加密字符串(密码)算出来的。HMAC算法有多种,而在jwt token的头部已经声明了,是HS256,即使用sha256计算出的HMAC。
计算方法是: php -r "echo base64_encode(hash_hmac('sha256', '头部.载荷','密码', true));"
。
等等,怎么不完全一样?查了资料后才知道,原来jwt要用在http协议里,可以在URL当做GET请求参数,那么就有必要进行转义。于是+替换成-,/替换成_,而=是可以直接省略的。于是完整的计算方法是:php -r "echo str_replace('=', '', strtr(base64_encode(hash_hmac('sha256', '头部.载荷', '密码', true)), '+/', '-_'));"
现在就完全一样了,搞定!
搞明白了jwt token回来看我最开始的问题,这个生产环境的token是谁的?既然只有签名是密文,载荷是明文,那真是易如反掌,很快我通过载荷就知道了这个token对应的用户id,通过数据库查询id找到了用户名,啊原来这个token是“管理员”这个小可爱的,找他。
出于好奇,找他之前,我想看看这个代码是怎么选择密码的,别有什么其他漏洞。查阅代码后发现,原来它选择了用户的盐(salt)作为签名加密的密码,也可以吧。
注:jwt token载荷里有创建时间,可以用于判定token是否过期。通过验证签名部分判断jwt是否合法,避免伪造。
好了,收工。