我也能修改轮子的源码了?

前因

愉快的周一(bushi,同时也是入职的第三周,本来还想着继续愉快的摸鱼,因为实在没有业务线能让我继续参与,但今天突然收到了 leader 的任务,在一个.net 的老系统的基础上,能够用 Spring 继续开发新的 CRUD,不就是换个铲子做农民工吗,难不倒我练习 CRUD 两年半的哥哥。这里讲一下我具体的任务,相信看完解决思路以后可能会对你有一点不一样的启发,那真是我荣幸之至。

任务

公司目前使用 .net 作为后端开发接口,因为后续业务线原因,需要本人,对!就是本人用 Java 开发一套新的服务,并能够和老系统无缝对接。乍一听好像不难,来我仔细给你捋捋,请继续往下看,有大坑!

  • 老系统在登录时会返回前端 token,这个 token 是由 .net 中的 jwt 工具生成。
  • 前端储存 token,并在后续受保护的接口请求 Header 中携带 token
  • 我的任务: 在 SpringBoot 中使用 jjwt 库去验证这个 token,如果合法则继续执行业务代码,反之抛出验证错误异常

到这里应该没任何困难吧啦~

有聪明可爱一胎拔个的宝宝举手说了:「帅帅,你最难的一步就是跪下来求求你的 leader 把生成 token 时的 key 扔你脸上就行了,生成 jwt 和验证 jwt 应该是和平台无关的」

帅帅:你不说我一个月之后也能想到,哼(傲娇

到此为止,我快速的露出了以下代码(脱敏):

java 复制代码
String key = "loveueveryday"
String token = "eyfdskhjfksdhkfj.pociopsahfhue.dsafhshfj" // 猫在键盘上画的
try {
    Claims claims = Jwts
        .parser()
        .setSigningKey(key) // 设置签名时的 key
        .parseClaimsJws(token) // 设置需要验证的 token
        .getBody();
} catch (Exception e) {
    log.error("token 验证失败");
}

我自信的按下 Run 按钮,举起茶杯,等待着 Terminal 的成功绿色,看着跳动的代码,轻哼着: 就这,不难,不费力,不费心,不对劲!!! 怎么爆红了喂,异常显示如下:

java 复制代码
io.jsonwebtoken.SignatureException: Unsupported signature algorithm 'http://www.w3.org/2001/04/xmldsig-more#hmac-sha256'

我的额头出现微汗,开启了专注模式,解决不了这个问题的话,第二天就要因为左脚先跨进公司被劝退了呜呜呜

定位问题

今天主角: 很多项目(若依等) jwt 生成与验证的库: JJWT

一个小技巧,如果你的代码出现了意料之外的 bug,先不要着急,点进异常堆栈最近的一个去看看源码,看看异常是怎么被抛出来的,有的同学可能对源码存在一种天生的畏惧心理,想着大佬写的源码岂是我等凡夫俗子能读懂的?其实没必要,跟着我的脚步,你会发现源码其实也就是那么一回事,你我这样的普通人甚至可以修改源码来定制功能,只是使用的人多了,确保了这些轮子不会轻易出现问题。

  • 跟进 jjwt 抛出异常的源码后发现:
java 复制代码
public enum SignatureAlgorithm { 
	NONE("none", "No digital signature or MAC performed", "None", null, false),
    /** JWA algorithm name for {@code HMAC using SHA-256} */
    HS256("HS256", "HMAC using SHA-256", "HMAC", "HmacSHA256", true),
    /** JWA algorithm name for {@code HMAC using SHA-384} */
    HS384("HS384", "HMAC using SHA-384", "HMAC", "HmacSHA384", true),
    ...
    // 在枚举中寻找是否存在 header 中的 alg 算法类型,存在则直接返回
    public static SignatureAlgorithm forName(String value) throws SignatureException {
        for (SignatureAlgorithm alg : values()) {
            if (alg.getValue().equalsIgnoreCase(value)) {
                return alg;
            }
        }

        // 注意看这个异常,不就是上面抛出来的吗?
        throw new SignatureException("Unsupported signature algorithm '" + value + "'");
    }
}

现在大概有一点思路了,可能是 C# 生成 jwt 时写进 header 里的 alg 算法,在 JJWT 这个库中找不到? 为了避免有人对 jwt 的部分概念不理解,这里稍微提一下哈~

偷窥 JWT

一个 JWT 分为三个部分: 1. header 2. payload 3. signature.

你有没有疑惑过,服务器是怎么确定客户端传来的 jwt 的可靠的呢?跟着我:

  1. 服务端生成 jwt 时需要一个 key,这个 key 是绝对保密的,绝对不能泄漏
  2. 登录时:服务端将组装好的 payload(通常是标识用户唯一身份字段: userId等),和需要用到的签名算法(常用 HS256,记住,后面要考),将签名算法以 alg: HS256 的键值对形式放进 header 中,再用 key + 签名算法生成出一个签名出来,也就是上面的 signature
  3. 接口调用时: 服务端取出 token,将 payload 和 header 又拿去签名一次,得到验证时的签名。将验证时的签名与 token 中的签名一对比,如果一模一样,则说明这个 token 是可靠的,可以进行后面的业务;如果不同就有意思了,可能是某个小黑篡改了 token 中的值,但不知道服务端的 key,又不能篡改 token 中的签名

这下你对 token 的全流程有点概念了吧?那我们继续:cry:

回到主线

我们能轻易的发现: C# 生成 jwt 的算法好像也是 HS256?你仔细看看http://www.w3.org/2001/04/xmldsig-more#hmac-sha256 最后缩写是不是 HS256?Google 了一下还真是,那么目前大概就清晰起来了:

C# 生成 jwt 也是使用的 HS256 签名算法,并且这个算法 JJWT 是支持的。不过由于可能由于不同库的规范的原因,在 C# 和 JJWT 之间,header 中的 alg 字段就有了差异,下面看一看对比:

json 复制代码
// C# header:
{
  "alg": "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256",
  "typ": "JWT"
}

// JJWT header:
{
  "alg": "HS256",
  "typ": "JWT"
}

那现在问题知道了,应该怎么解决呢?

定制 JJWT 源码

首先,对于开源轮子,是不建议遇到问题一上来就修改源码的,最佳的是通过轮子提供的扩展点达到你要实现的功能目的,那为什么我要在 JJWT 的源码上动手脚呢?是因为我看了 JJWT 的源码后,发现验证 token 时是不支持自定义算法的。换句话说,如果 header 中 alg 的值在上面的算法枚举找不到的话,就会抛出异常。即使大家都知道这就是 HS256 签名算法,谁管你规范不规范的问题(如果有哥哥指出这个扩展点的话,直接说,不要担心我会哭

修改源码目的:

让 JJWT 能够知道 http://www.w3.org/2001/04/xmldsig-more#hmac-sha256 其实就是 HS256,直接用他签名就是了,不要给我抛出什么异常。

步骤:
  • 拉取 jjwt 源码

git clone https://github.com/jwtk/jjwt.git

  • 切换指定版本

git checkout 0.9.1

  • 安装依赖

  • 定位目标代码(其实只需在上面的枚举类中添加一个枚举就行,看我操作)

java 复制代码
public enum SignatureAlgorithm {

    // JWA name for {@code No digital signature or MAC performed} */
    NONE("none", "No digital signature or MAC performed", "None", null, false),

    // JWA algorithm name for {@code HMAC using SHA-256} */
    HS256("HS256", "HMAC using SHA-256", "HMAC", "HmacSHA256", true),
    
    // 添加的枚举,将其与 HS256 进行映射 
    HS256_FOR_CS("http://www.w3.org/2001/04/xmldsig-more#hmac-sha256", "HMAC using SHA-256", "HMAC", "HmacSHA256", true),
    ...
        
    public static SignatureAlgorithm forName(String value) throws SignatureException {
        // 这时就能成功找到对应的算法了
        for (SignatureAlgorithm alg : values()) {
            if (alg.getValue().equalsIgnoreCase(value)) {
                return alg;
            }
        }
        
        throw new SignatureException("Unsupported signature algorithm '" + value + "'");
    }
    
}

编译 打包 测试 成功!

文末

可以看到修改源码只有一小部分,最想表达的其实是定位问题到解决问题的思路。最后,如果对于文章有更好的建议,请指出,感谢您的时间,如果对你有所启发,将是我的荣幸之至。

相关推荐
Marktowin16 分钟前
Mybatis-Plus更新操作时的一个坑
java·后端
赵文宇40 分钟前
CNCF Dragonfly 毕业啦!基于P2P的镜像和文件分发系统快速入门,在线体验
后端
程序员爱钓鱼1 小时前
Node.js 编程实战:即时聊天应用 —— WebSocket 实现实时通信
前端·后端·node.js
Libby博仙2 小时前
Spring Boot 条件化注解深度解析
java·spring boot·后端
源代码•宸2 小时前
Golang原理剖析(Map 源码梳理)
经验分享·后端·算法·leetcode·golang·map
小周在成长2 小时前
动态SQL与MyBatis动态SQL最佳实践
后端
瓦尔登湖懒羊羊2 小时前
TCP的自我介绍
后端
小周在成长2 小时前
MyBatis 动态SQL学习
后端
子非鱼9212 小时前
SpringBoot快速上手
java·spring boot·后端
我爱娃哈哈2 小时前
SpringBoot + XXL-JOB + Quartz:任务调度双引擎选型与高可用调度平台搭建
java·spring boot·后端