学无先后,达者为师

网站首页 编程语言 正文

jwt Claims token 秘钥稍有不同也能解析成功 signWith setSigningKey

作者:阿亮_1024 更新时间: 2022-02-27 编程语言

问题描述

使用io.jsonwebtoken.Jwts构造了一个token,在解析这个token时,发现解析秘钥和构建秘钥不完全相同也可以成功解析,代码如下

签发token

    /** 测试生成token */
    @Test
    public void testJwt() {
        JwtBuilder jwtBuilder = Jwts.builder()
                // 唯一ID {"":""}
                .setId("888")
                // 接受的用户 {"sub":"Rose"}
                .setSubject("Rose")
                // 签发时间 {"iat":"。。。"}
                .setIssuedAt(new Date())
                // 签名算法 及秘钥
                .signWith(SignatureAlgorithm.HS256, "xxxx");
        // 签发token
        String token = jwtBuilder.compact();
        System.out.println(token);

        String[] split = token.split("\\.");
        // 头部
        System.out.println(Base64Codec.BASE64.decodeToString(split[0]));
        // 载荷
        System.out.println(Base64Codec.BASE64.decodeToString(split[1]));
        // 算法及秘钥 这个会乱码
        System.out.println(Base64Codec.BASE64.decodeToString(split[2]));
    }

解析token

    /** 解析token */
    @Test
    public void testParseToken() {
        String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiJSb3NlIiwiaWF0IjoxNjMyNzMxMzYyfQ.9GZJUoTNwJuMh5tFMd3giJh36YNuCnFuWsDKONOg2C8";
        // 得到的是荷载
        Claims claims = (Claims) Jwts.parser()
                // 解析时的秘钥一定要和签发时秘钥相同,但是发现这了的秘钥为4-7个x都是可以成功解析的
                .setSigningKey("xxxx")
                // .parse(token)
                .parseClaimsJws(token)
                .getBody();
        System.out.println(claims);
    }

在签发时我的秘钥是四个x,但是在解析时,4-7个x都能成功解析,我很不理解,虽然不是什么秘钥都可以成功解析,但是近似的就能解析也太不安全了吧

问题分析

我第一时间想到的是去网上查,没有想到第一时间自己看源码,因为可能源码一时半会看不出来个啥。去网上查了一下,果然查到了一些有用的东西,然后我点进源码看了一下,一下子就清楚了。

先查看设置秘钥的地方,io.jsonwebtoken.JwtBuilder有一个默认的实现类io.jsonwebtoken.impl.DefaultJwtBuilder,我们看看他的signWith方法,signWith有三个重载形式,我们只看我们调用的那个就行了
signWith方法
关键的地方我标出来了,signWith拿到秘钥之后是先将秘钥用base64解码之后再进行操作的。

然后再看解析token时的地方,io.jsonwebtoken.JwtParser也有一个默认的实现类io.jsonwebtoken.impl.DefaultJwtParser,设置秘钥的方法setSigningKey也有三个重载形式,我们使用的是下面这个
setSigningKey方法
可以看到,setSigningKey方法也将拿到的秘钥用base64解码了,咋一看好像没什么问题,解码就解码呗,反正两边都解了,同一个字符串,解码过肯定是一样的结果,但问题恰巧就出现在这里。

虽然相同的字符串经过base64解码过后也一定是相同的,但是不同的字符串经过base64解码过后也可能是相同的!!

下面是我的测试
base64测试
可以看到,我测试了三种不同地方的工具类,在将xxxx和xxxxx按照base64解码时,都解码成相同的byte数组了。在jwt设置秘钥和解析秘钥时,使用的是第一种,所以就不难理解,为啥秘钥即便不完全相同,只要近似也能解析。

解决问题

虽然搞清楚了原理,那怎么解决这个问题呢?或者这是jjwt的一个或base64的bug?是不是base64的bug我不知道,但是一定不是jjwt的bug,只是我们调用api是没有按照人家要求调用。再来看看构建token时设置秘钥的方法
方法描述
人家方法的变量名叫:base64EncodedSecretKey,就是想让你传一个经过base64编码过后的字符串。接口上也有明确的注释:
接口注释
在解析token时设置秘钥的方法也有同样的注释。如果按照api要求来调用,就没有问题了,因为base64是对称加密的,解码被加密过后一样的字符串,得到的肯定是两个一样原始内容。

反思总结

在调用api时,没有好好的看别人的文档或是注释,记大过一次。

联想拓展

我对base64了解没那么深,只知道是一个对称加密的算法,但是也会感到奇怪,为什么对不同的字符串进行解码时,能得到相同的结果呢?

我只能尝试这么理解:Base64在加密时,是通过一步一步加密的,相同的东西,在加密时经过的步骤都是相同的,所以最终加密出来也是相同的。但是解密则有点不同,什么不同呢?如果你是经过我Base64加密过后,你再来经过我解密,我能按照加密过程的逆过程,一步一步把你还原成原来的样子。但是如果你压根就没有经过我Base64加密,你却要来走我Base64的还原过程,那我在还原的过程中,有的步骤都没法走,直接跳过了,最后把你“还原”。所以那些近似的、不是经过Base64加密的字符串,在经过Base64解密时,都有一些相同步骤跳过了,所以最终解密过后得到了相同的东西。当然,这都是我自己的理解。

另外,在测试过程中,我发现,有的地方的Base64工具类,测试的结果不一样,上面测试了三个base64解码,分别是:

  • io.jsonwebtoken.impl.TextCodec.BASE64
  • io.jsonwebtoken.impl.Base64Codec
  • cn.hutool.core.codec.Base64

三者在解密五个x时,结果都是一样的,也都和4个x的解密相同。但是在解密6个x和7个x时,就有所不同了

在这里插入图片描述

前两者解密6个x和7个x时,得到的结果依然和4个x是相同的,但是第三者解密出来是不同的,不知道这是为啥。

文章参考:如果SigningKey稍有不同,JJWT解析不会失败

原文链接:https://blog.csdn.net/ql_7256/article/details/120522555

栏目分类
最近更新