JWT实战之升级Java JWT

news2024/11/16 11:45:18

概述

关于JWT的基础概念,如JWT组成部分,以及入门实战,如:如何生成Token、如何解析Token、怎么加入自定义字段等,可参考JWT入门教程。

如前文提到的blog所述,大多数公司都会使用如下(版本)的Maven依赖:

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

看一下Maven仓库:
在这里插入图片描述
可以看到有64个CVE安全漏洞!很多!!

使用JWT(以及其他安全框架,如Spring Security或Spring Security OAuth2)的目标是加强应用的认证和鉴权,结果Java JWT工具包本身有这么多CVE安全隐患,有点搞笑了。

于是产生依赖库升级这样一个技术改造需求,本文记录升级遇到的问题。

问题

GAV变更

谈到依赖三方库升级,必然需要借助于Maven仓库。搜索不难发现,artifactId发生变更,需要引入如下依赖:

<properties>
    <jjwt.version>0.12.5</jjwt.version>
</properties>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>${jjwt.version}</version>
</dependency>

编译问题

在这里插入图片描述
如上截图,编译报错是比@deprecated API更严重的问题。使用过期的,即将废弃的API,即@deprecated API,IDEA会给出屎黄色的warning提示。一般而言,某个过期API,再经过几次版本升级迭代后,就会变成removed API,即编译报错,也就是上面截图看到的红色。执行mvn compile失败,应用启动失败。

代码片段如下:

public static Claims getClaimsFromToken(String token) {
    Claims claims;
    try {
        claims = Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
    } catch (Exception e) {
        claims = null;
    }
    return claims;
}

关于如何替换被标记为@deprecated或removed的API,一般都是看源码。

比如上面的setSigningKey源码注释里有提到in favor of verifyWith(SecretKey) as explained in the above Deprecation Notice and will be removed in 1.0.0.告知,故而可以考虑使用verifyWith(SecretKey)来作为替换。

但是如果版本升级跨度太大(API被废弃后标红,根本就不能通过IDEA去查看源码),或是开源代码维护者没有提供替换API等解决方案,则比较麻烦。此时一般都是去查看官方文档,或Google搜索。好在Java JWT提供维护良好的文档。

调整后的代码片段如下:

public static Claims getClaimsFromToken(String token) {
    SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret));
    return Jwts.parser()
            .verifyWith(key)
            .build()
            .parseSignedClaims(token)
            .getPayload();
}

启动失败DefaultJwtBuilder

解决上面一个编译问题后,实际上还有另外一个废弃API的问题,后文再提。解决编译问题后,优先级自然是看应用能否启动,postman接口测试能否请求成功。结果遇到应用Debug模式启动失败的报错:

io.jsonwebtoken.lang.UnknownClassException: Unable to load class named [io.jsonwebtoken.impl.DefaultJwtBuilder] from the thread context, current, or system/application ClassLoaders.  All heuristics have been exhausted.  Class could not be found.  Have you remembered to include the jjwt-impl.jar in your runtime classpath?

报错提示已经很明显,通过查看maven私服仓库。稍加分析可得出结论:自0.10.0版本后,之前的一个依赖拆开为多个依赖:

加入以下依赖,重启应用,报错消失,应用可以正常启动。

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>${jjwt.version}</version>
</dependency>

解决得到问题。

WeakKeyException HS512 algorithm

应用启动成功,postman请求login接口,结果遇到如下报错信息:

io.jsonwebtoken.security.WeakKeyException: The signing key's size is 72 bits which is not secure enough for the HS512 algorithm.  The JWT JWA Specification (RFC 7518, Section 3.2) states that keys used with HS512 MUST have a size >= 512 bits (the key size must be greater than or equal to the hash output size).  Consider using the io.jsonwebtoken.security.Keys class's 'secretKeyFor(SignatureAlgorithm.HS512)' method to create a key guaranteed to be secure enough for HS512.  See https://tools.ietf.org/html/rfc7518#section-3.2 for more information.

大意就是使用的加密等级强度不够。之前遇到这类提示,一般都会选择性忽视;毕竟,能Run起来就说明能Work;warning嘛,有时间再去优化。结果好家伙,升级依赖后,直接报错,意思很明显:必须要提高密钥长度或使用破解难度更大的加密算法。

看了下代码,搜索HS512,发现是生成token的如下代码片段报错:

public static String generateToken(Map<String, Object> claims) {
    return Jwts.builder()
            .setClaims(claims)
            .setExpiration(generateExpirationDate())
            .signWith(SignatureAlgorithm.HS512, secret)
            .compact();
}

上面还提到有个废弃API没有优化,正好就是此处被废弃的API:
在这里插入图片描述
那先解决废弃API的问题吧。搜索GitHub官方文档,优化调整后的代码片段如下:

public static String generateToken(Map<String, Object> claims) {
	SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(SECRET));
	return Jwts.builder()
        .claims(claims)
        .expiration(generateExpirationDate())
        .signWith(key)
        .compact();
}

这里额外提一句:上面的报错提示是HS512算法。

一开始并没有找到HS512算法的示例代码,并没有做到升级前后【本质】保持不变的参考性原则。

WeakKeyException HMAC-SHA algorithm

没有找到HS512算法示例,使用Keys.hmacShaKeyFor()方法,postman接口调试报错信息如下:

io.jsonwebtoken.security.WeakKeyException: The specified key byte array is 72 bits which is not secure enough for any JWT HMAC-SHA algorithm. The JWT JWA Specification (RFC 7518, Section 3.2) states that keys used with HMAC-SHA algorithms MUST have a size >= 256 bits (the key size must be greater than or equal to the hash output size).  Consider using the Jwts.SIG.HS256.key() builder (or HS384.key() or HS512.key()) to create a key guaranteed to be secure enough for your preferred HMAC-SHA algorithm.

根据报错提示,是HMAC-SHA算法,使用的密钥是private static final String SECRET = "ThisIsASecret";。长度不够?

于是无脑复制加长密钥,SECRET = "ThisIsASecretThisIsASecretThisIsASecretThisIsASecret",结果还真解决报错。

json Serializer

继续调试postman接口,又遇到报错信息如下:

io.jsonwebtoken.impl.lang.UnavailableImplementationException: Unable to find an implementation for interface io.jsonwebtoken.io.Serializer using java.util.ServiceLoader. Ensure you include a backing implementation .jar in the classpath, for example jjwt-jackson.jar, jjwt-gson.jar or jjwt-orgjson.jar, or your own .jar for custom implementations.

不难得知和上面的问题,加入以下依赖,重启应用解决问题:

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>${jjwt.version}</version>
</dependency>

Compact JWT strings may not contain whitespace

现在登录接口login已经调试成功,postman里可以看到接口成功返回的JWT Token。后续的所有请求都需要带着这个Token。

继续调试其他接口,又遇到一个报错:Compact JWT strings may not contain whitespace.

有点懵啊。自认为我的英文阅读理解能力还挺不错啊,理智告诉我:JWT字符串不得包含空格

事实上,我生成的Token形式如下:eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiLDtcKHc1jCmcOCwrHDhcOMw5PDr8OPXHUwMDBCwobDim0iLCJleHAiOjE3MTAxNTM1OTd9.sKlGrAF7FFYk8hUZD7PeRddOG6azm_gNgnZ9a9mRu70

通过JWT在线解密网站,这个JWT可以成功解密:
在这里插入图片描述
说明生成的JWT Token是没有问题的。

回过头来,再仔细看看postman设置Token的选项,选择Bearer Token并没有问题啊:
在这里插入图片描述
空格??Bearer Token格式是Bearer jwt,经过排查,发现一个很傻的问题:

// 少了个空格
public static final String TOKEN_PREFIX = "Bearer ";

Bearer Token和JWT Bearer

与此同时,在排查上面的空格问题时,发现postman功能强大支持好几种格式的Token。
在这里插入图片描述
其中Bearer Token是我们最常使用的。JWT Bearer是什么?选择JWT Bearer后,发现如下下拉列表
在这里插入图片描述
等等,这里的算法列表,不正好有一个上面提到的HS512算法吗?

继续研究HS512的初始化SecretKey代码片段,还是在GitHub官网找到代码片段:

// SecretKey secret = Keys.hmacShaKeyFor(Decoders.BASE64.decode(SECRET));
SecretKey secret = Jwts.SIG.HS512.key().build();

使用下面这个HS512算法以及SecretKey这个API后,则无需再额外定义一个密钥字符串常量SECRET,当然也就不存在上面提到的密钥长度问题。

事实上,在写此文的过程中,认识到postman也在建议我们使用HS512加密算法,而不是HMAC-SHA算法。

使用HS512加密算法,生成的JWT Token更长一些:eyJhbGciOiJIUzUxMiJ9.eyJ1c2VySWQiOiLDtcKHc1jCmcOCwrHDhcOMw5PDr8OPXHUwMDBCwobDim0iLCJleHAiOjE3MTAxNzIyMTF9.ESVmAmm8rw9UGJG7he2EfTKz4xvYO5C5SSmkLbvEaK8VafKtOfPp64q8ONwDmQUoXsh0vn03ONFEeaQb9HqU_w

postman使用JWT Bearer,然后选择HS512算法,填入上面生成的Token,但是又遇到报错。

JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted

承接上一个章节,又遇到另一个报错,还真是无穷无尽啊:
JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.

TODO:未解决。还是使用HMAC-SHA算法,及postman使用Bearer Token吧。。

加密自定义字段

另外,再多扯几句。不难发现上面在线解密JWT的截图里,解密后payload里有个自定义字段userId

看到熟悉的乱码?别慌!!

由于JWT可以轻易被截取,并能被解密。但是在真实的业务场景开发中,我们又经常会遇到需要在JWT Token里加塞字段的需求,去渠道,商户Id等。如果业务交互需要某个敏感字段,如手机号,怎么办呢?加密一下:

HashMap<String, Object> claims = new HashMap<>();
// put any data in the map
map.put(USER_ID, EncryptUtil.encrypt(userId));

userId一般情况下都是无意义的UUID,上面的代码片段仅仅是demo示例。

附录

附录源码:

@Slf4j
public class JwtUtil {
    public static final String TOKEN_PREFIX = "Bearer ";
    public static final String HEADER_STRING = "Authorization";
    public static final String USER_ID = "userId";
    private static final Long EXPIRATION_TIME = 3600000L; // 1 hour
    private static final String SECRET = "ThisIsASecretThisIsASecretThisIsASecretThisIsASecret";

    public static String generateToken(String userId) {
        HashMap<String, Object> map = new HashMap<>();
        // put any data in the map
        try {
            map.put(USER_ID, EncryptUtil.encrypt(userId));
        } catch (Exception e) {
            log.warn("Encryption failed.", e);
            throw new RuntimeException("Encryption failed");
        }
        return generateToken(map);
    }

    public static String generateToken(Map<String, Object> claims) {
        SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(SECRET));
        return Jwts.builder()
                .claims(claims)
                .expiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                .signWith(key)
                .compact();
    }

	public static Claims getClaimsFromToken(String token) {
	    SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret));
	    return Jwts.parser()
	            .verifyWith(key)
	            .build()
	            .parseSignedClaims(token)
	            .getPayload();
	}
}

参考

  • 官方GitHub文档

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1510046.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C#,入门教程(26)——数据的基本概念与使用方法

上一篇: C#,入门教程(25)——注释(Comments)你会吗?看多图演示,学真正注释。https://blog.csdn.net/beijinghorn/article/details/124681888 本文所述的知识基本上适用于C/C++,java等其他语言。 数据是程序的基础,算法是程序的栋梁。 徒弟们交作业的之后,一般都会有…

【Sql】数据库的三范式?MySQL数据库引擎有?InnoDB与MyISAM的区别

目录 数据库的三范式&#xff1f; MySQL数据库引擎有&#xff1f; InnoDB与MyISAM的区别 数据库的三范式&#xff1f; 第一范式&#xff1a;是数据库最基本的要求&#xff0c;列不可再分 第二范式&#xff1a;行可以唯一区分&#xff0c;主键约束 第三范式&#xff1a;是在…

【io.net】问题汇总

【io.net】问题汇总 大家最近挖挖的如火如荼&#xff0c;可是不论是社区活动积分和参与挖矿积分&#xff0c;大家遇到了很多类似问题&#xff0c;重复解决。 因此我这里整理了一下常见的相关问题&#xff0c;大家可以一站式找到解决方案。解决方案主要分为运营和挖矿两个两面…

【CMake G++ GCC】在 Linux 环境中编译 C++ 源码

在 Linux 环境中编译 C 源码 C 技术栏 文章演示了在 乌班图 环境下编译mathematical-expression-cpp 动态库源码,&#xff01; 目录 文章目录 在 Linux 环境中编译 C 源码目录介绍前置准备apt 更新下载并进入源码包 开始编译源码包修改 CMakeList 文件开始进行 makeFile 的生…

AIGC——ConsiStory无需训练LoRA快速实现主题一致地文本到图像生成技术

简介 当前的图像生成技术大多数采用随机采样&#xff0c;这导致每次生成的图像都有所不同&#xff0c;特别是在生成连续图像时难以保持一致性。 举例来说&#xff0c;尝试用AI生成一组图像连环画时&#xff0c;即使使用相似的提示词&#xff0c;也难以达到理想效果。 尽管DA…

7-18 两个数的简单计算器

本题要求编写一个简单计算器程序&#xff0c;可根据输入的运算符&#xff0c;对2个整数进行加、减、乘、除或求余运算。题目保证输入和输出均不超过整型范围。 输入格式&#xff1a; 输入在一行中依次输入操作数1、运算符、操作数2&#xff0c;其间以1个空格分隔。操作数的数…

嵌入式学习第二十七天!(TCP并发模型)

TCP并发模型&#xff1a; 1. TCP多线程模型&#xff1a; 缺点&#xff1a;创建线程会带来资源开销&#xff0c;能够实现的并发量比较有限。 2. IO模型&#xff1a; 1. 阻塞IO&#xff1a; 没有数据到来时&#xff0c;可以让任务挂起&#xff0c;节省CPU资源开销&#xff0c;提…

C# MES通信从入门到精通(1)——串口传输文件

前言: 在上位机软件开发领域,有一些工厂的mes系统需要我们通过串口发送文件的方式把一些图片或者检测数据csv文件等发送给服务器,这种方式是一些比较旧的工厂采用的方式,但是这种方式也是存在的,本文就是讲解如何使用串口发送文件详情见下文。 1、串口发送文件思路 将需…

Python-Pong-Game

我还加了音效&#xff0c;类似于小时候游戏机上的弹球游戏 import os import turtle import pygame#初始化pygame pygame.init()#加载声音文件 bounce_sound pygame.mixer.Sound("bounce.mp3")wn turtle.Screen() wn.title("Pong by ") wn.bgcolor(&qu…

docker学习进阶篇

一、dockerfile解析 官方文档&#xff1a; Dockerfile reference | Docker Docs 1.1、dockfile是什么&#xff1f; dockerfile是用来构建docker镜像的文本文件&#xff0c;由一条条构建镜像所需的指令和参数构成的脚本。 之前我们介绍过通过具体容器反射构建镜像(docker comm…

幻兽帕鲁游戏服务器多少钱?2024最新报价单请查收

游戏服务器租用多少钱一年&#xff1f;1个月游戏服务器费用多少&#xff1f;阿里云游戏服务器26元1个月、腾讯云游戏服务器32元&#xff0c;华为云26元&#xff0c;游戏服务器配置从4核16G、4核32G、8核32G、16核64G等配置可选&#xff0c;游戏专业服务器公网带宽10M、12M、15M…

《深入Linux内核架构》第1章 简洁和概述

目录 1.1 内核的任务 1.2 实现策略 1.3 内核的组成部分 ​编辑1.3.1 进程、进程切换、调度 1.3.2 UNIX 进程 1.3.3 地址空间和特权级别 1.3.4 页表 1.3.5 物理内存的分配 1.3.6 计时 1.3.7 系统调用 1.3.8 设备驱动程序 1.3.9 网络 1.3.10 文件系统 1.3.11 模块和…

java guide 八股

Java语言特点 简单易学、面向对象&#xff08;继承、封装、多态&#xff09;、平台无关性&#xff08;Java虚拟机jvm&#xff09;、支持多线程、可靠、安全、高效、支持网络编程、编译与解释共存 JVM&#xff1a;Java虚拟机&#xff08;跨平台的关键&#xff09; JRE&#xff…

ROS——其他ROS版本安装

1.2.6 资料:其他ROS版本安装 我们的教程采用的是ROS的最新版本noetic&#xff0c;不过noetic较之于之前的ROS版本变动较大且部分功能包还未更新&#xff0c;因此如果有需要(比如到后期实践阶段&#xff0c;由于部分重要的功能包还未更新&#xff0c;需要ROS降级)&#xff0c;也…

Spring Cloud集成nacos配置中心

1.添加Nacos Config依赖 打开nacos-config-demo的pom.xml文件并添加以下两个依赖项 项目的配置文件中通常包括数据库连接配置项、日志输出配置项、Redis连接配置项、服务注册配置项等内容&#xff0c;如spring-cloud-alibaba-nacos-config-base-demo项目中就包含数据库连接配置…

控件交互触屏操作

控件交互 print(driver.find_element(By.ID, com.xueqiu.android:id/tv_agree).is_enabled()) # 判断元素是否可点击 print(driver.find_element(By.ID, com.xueqiu.android:id/tv_agree).is_display()) # 判断元素是否可显示 print(driver.find_element(By.ID, com.xueqiu.…

【梳理】k8s使用Operator搭建Flink集群(高可用可选)

文章目录 1. 架构图2. helm 安装operator3. 集群知识k8s上的两种模式&#xff1a;Native和Standalone两种CR 4. 运行集群实例Demo1&#xff1a;Application 集群Demo2&#xff1a;Session集群优劣 5. 高可用部署问题1&#xff1a;High availability should be enabled when sta…

spring boot 使用 webservice

spring boot 使用 webservice 使用 java 自带的 jax-ws 依赖 如果是jdk1.8,不需要引入任何依赖&#xff0c;如果大于1.8 <dependency><groupId>javax.jws</groupId><artifactId>javax.jws-api</artifactId><version>1.1</version&g…

JVM-3

HotSpot虚拟机对象 我在网上看了很多相关的文章&#xff0c;发现在创建对象和对象的结构中内容都不太一样&#xff0c;一些关键字也很不同&#xff0c;于是我通过参考《深入理解Java虚拟机》这本书&#xff0c;自己总结了一篇。 1.对象的创建 当JVM收到一条创建对象的字节码…