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

前因

愉快的周一(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 + "'");
    }
    
}

编译 打包 测试 成功!

文末

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

相关推荐
落落落sss5 分钟前
MQ集群
java·服务器·开发语言·后端·elasticsearch·adb·ruby
大鲤余35 分钟前
Rust,删除cargo安装的可执行文件
开发语言·后端·rust
她说彩礼65万43 分钟前
Asp.NET Core Mvc中一个视图怎么设置多个强数据类型
后端·asp.net·mvc
陈随易1 小时前
农村程序员-关于小孩教育的思考
前端·后端·程序员
_江南一点雨1 小时前
SpringBoot 3.3.5 试用CRaC,启动速度提升3到10倍
java·spring boot·后端
酸奶代码1 小时前
Spring AOP技术
java·后端·spring
代码小鑫1 小时前
A034-基于Spring Boot的供应商管理系统的设计与实现
java·开发语言·spring boot·后端·spring·毕业设计
paopaokaka_luck2 小时前
基于Spring Boot+Vue的多媒体素材管理系统的设计与实现
java·数据库·vue.js·spring boot·后端·算法
程序猿麦小七2 小时前
基于springboot的景区网页设计与实现
java·spring boot·后端·旅游·景区
蓝田~2 小时前
SpringBoot-自定义注解,拦截器
java·spring boot·后端