Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
JWT官网: https://jwt.io/
JWT(Java版)的github地址:https://github.com/jwtk/jjwt
之前写过两篇关于JWT的文章,如下链接
其中重点要提出的是,JWT存储的是明文,因此不能存储重要信息,或者可以进行加密。
在解决一个问题时,在工程A中生成了JWT的Token,由于JWT存储的是明文,解析是没有问题的,但是重点是效验,在工程B中,因为引入了HuTool工具箱(Hutool参考文档),当我使用其方法进行Token验证时,发现验证是失败的。
例如,在工程A中生成了一个Token为:
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqYXZh5bCP5by6IiwiZXhwIjoxNjgxMDI0NjM5LCJpYXQiOjE2Nzg0MzI2Mzl9.wDGa4FWi9Ok5WlWvhvhrorvjgdd9R1rYWu8nZLWANWY
如果解析,可以正常解析到内容,在A中也是验证正常的。
但是在工程B中,使用HuTool来验证一下这个Token,发现验证失败。
package com.example.springboot; import cn.hutool.jwt.JWTUtil; public class JwtTest { public static void main(String[] args) { String token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqYXZh5bCP5by6IiwiZXhwIjoxNjgxMDI0NjM5LCJpYXQiOjE2Nzg0MzI2Mzl9.wDGa4FWi9Ok5WlWvhvhrorvjgdd9R1rYWu8nZLWANWY"; String key = "Bbek6Qo2M!cpclGH"; System.out.println(JWTUtil.verify(token, key.getBytes())); } }
在工程A中引入了JJWT工具包
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
然后跟踪工程A中JWT生成Token时,处理密钥的代码
在这里,我们已经看到了端倪,它变量的名字为base64EncodedSecretKey,把代码跟下去看代码实现
@Override public JwtBuilder signWith(SignatureAlgorithm alg, String base64EncodedSecretKey) { Assert.hasText(base64EncodedSecretKey, "base64-encoded secret key cannot be null or empty."); Assert.isTrue(alg.isHmac(), "Base64-encoded key bytes may only be specified for HMAC signatures. If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead."); byte[] bytes = TextCodec.BASE64.decode(base64EncodedSecretKey); return signWith(alg, bytes); }
原来JJWT接接收密钥时,进行了BASE64处理,既然如此,HuTool工具箱也有关于Base64的操作,我们来改下代码
package com.example.springboot; import cn.hutool.core.codec.Base64; import cn.hutool.jwt.JWTUtil; public class JwtTest { public static void main(String[] args) { String token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqYXZh5bCP5by6IiwiZXhwIjoxNjgxMDI0NjM5LCJpYXQiOjE2Nzg0MzI2Mzl9.wDGa4FWi9Ok5WlWvhvhrorvjgdd9R1rYWu8nZLWANWY"; String key = "Bbek6Qo2M!cpclGH"; System.out.println(JWTUtil.verify(token, Base64.decode(key))); } }
运行发现,依然为False,难道同样是Base64,实现上有什么差别吗?跟进JJWT源码中关于Base64的实现
byte[] bytes = TextCodec.BASE64.decode(base64EncodedSecretKey);
往下跟,看到如下代码的注释
而HuTool工具箱的Base64操作为
/** * base64解码 * * @param base64 被解码的base64字符串 * @return 解码后的bytes */ public static byte[] decode(CharSequence base64) { return Base64Decoder.decode(base64); }
既然如此,再次修改代码
package com.example.springboot; import cn.hutool.jwt.JWTUtil; import io.jsonwebtoken.impl.TextCodec; public class JwtTest { public static void main(String[] args) { String token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqYXZh5bCP5by6IiwiZXhwIjoxNjgxMDI0NjM5LCJpYXQiOjE2Nzg0MzI2Mzl9.wDGa4FWi9Ok5WlWvhvhrorvjgdd9R1rYWu8nZLWANWY"; String key = "Bbek6Qo2M!cpclGH"; System.out.println(JWTUtil.verify(token, TextCodec.BASE64.decode((key)))); } }
运行验证为True。
总结:
虽然使用了同一种协议,但是底层实现的不同,将导致功能的不一致。比如你在工程A生成的Token在A中验证没问题,在工程B中生成的Token在B中验证没问题,但是反过来可能就有问题。
为什么是可能有问题?因为如果你使用了简单密钥,比如纯数字,你不会发现这个问题。
END
Java小强
未曾清贫难成人,不经打击老天真。
自古英雄出炼狱,从来富贵入凡尘。
发表评论: