JDK11,8引入不同版本的jjwt异常问题

2024-02-22 10:30

本文主要是介绍JDK11,8引入不同版本的jjwt异常问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

JJWT在JDK11,8及不同版本的处理问题

问题

原先在旧的项目中,用的是SpringCloudGateway2.0.4,对应的maven依赖是spring-cloud-starter-gateway:2.0.4.RELEASE,springboot的版本是2.0.6.RELEASE,jwt则直接是一个依赖全部引进来,如下所示

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

这个依赖在基于Java1.8版本是没有问题的,但是我们新项目用的是JDK11,这时候之前可以的加密方法就不能用了,有两种解决方案

jjwt加解密解决方案(JDK11中)

先不引入依赖看看,报什么异常,这里我原先有一个随机生成的加密secret,内容是“w-eyJleHAiOjE1NDMyMDUyODUsInN1YiI6ImFkbWluIiwiY3JlYXRlZCI6MTU0MDYxMzI4N”,在0.9.0中没有任何问题,然后再JDK11环境下进行加密,代码如下

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

这里我的secret现在可以是任意字符串,虽然它的源码中是public JwtBuilder signWith(SignatureAlgorithm alg, String base64EncodedSecretKey) ,但只是命名是base64EncodedSecretKey,原先的写法

生成token
/*** 从数据声明生成令牌** @param claims 数据声明* @return 令牌*/
private String generateToken(Map<String, Object> claims) {return Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, secret).compact();
}
解析token
/*** 从令牌中获取数据声明** @param token 令牌* @return 数据声明*/
private Claims getClaimsFromToken(String token) {Claims claims;try {claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();} catch (Exception e) {claims = null;}return claims;
}
相关的详细代码

JwtUser.java


import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.ToString;import java.io.Serializable;
import java.util.Collection;@ToString
public class JwtUser implements Serializable {private String uid;private String username;private String nickName;private String phone;private String password;private Integer state;private String sessionKey;private Collection authorities;public JwtUser() {}public JwtUser(String uid, String username, String nickName, String phone, String password, Integer state, String sessionKey, Collection authorities) {this.uid = uid;this.username = username;this.nickName = nickName;this.phone = phone;this.password = password;this.state = state;this.sessionKey = sessionKey;this.authorities = authorities;}public String getPhone() {return phone;}public void setPhone(String phone) {this.phone = phone;}public String getNickName() {return nickName;}public void setNickName(String nickName) {this.nickName = nickName;}public String getUid() {return uid;}public void setUid(String uid) {this.uid = uid;}public void setUsername(String username) {this.username = username;}public void setPassword(String password) {this.password = password;}public Integer getState() {return state;}public void setState(Integer state) {this.state = state;}public String getUsername() {return username;}@JsonIgnorepublic String getPassword() {return password;}@JsonIgnorepublic boolean isAccountNonExpired() {return true;}@JsonIgnorepublic boolean isAccountNonLocked() {return state == 1 || state == 3 || state == 4 || state == 5;}@JsonIgnorepublic boolean isCredentialsNonExpired() {return true;}@JsonIgnorepublic boolean isEnabled() {return true;}public String getSessionKey() {return sessionKey;}public void setSessionKey(String sessionKey) {this.sessionKey = sessionKey;}public Collection getAuthorities() {return authorities;}public void setAuthorities(Collection authorities) {this.authorities = authorities;}
}

JwtTokenUtil.java

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;import java.io.Serializable;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;/*** Description: Token工具类 Created on 2019/10/14 15:37** @author <a href="mailto: Tablo_Jhin1996@outlook.com">Tablo</a>* @version 1.0*/
@Data
@ConfigurationProperties(prefix = "jwt")
@EnableConfigurationProperties(JwtTokenUtil.class)
@Component
public class JwtTokenUtil implements Serializable {private static final String CLAIM_KEY_USER_ACCOUNT = "sub";private static final String CLAIM_KEY_CREATED = "created";/** 秘钥 */private String secret;/** 过期时间 */private Long expiration;private String tokenHeader;private String tokenHead;private String[] exceptUrl;private String[] mustUrl;/*** .Created on 17:22 2019/10/17 Author: Tablo.** <p>Description:[判定是否需要校验Token]** @param exceptUrls 排除的Url* @param path 请求路径* @return boolean*/public static boolean judgeIsCheck(String[] mustUrls, String[] exceptUrls, String path) {if (Arrays.stream(mustUrls).anyMatch(path::startsWith)) {return false;}return Arrays.stream(exceptUrls).anyMatch(path::startsWith);}/*** 从数据声明生成令牌** @param claims 数据声明* @return 令牌*/private String generateToken(Map<String, Object> claims) {return Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, secret).compact();}/*** 从令牌中获取数据声明** @param token 令牌* @return 数据声明*/private Claims getClaimsFromToken(String token) {Claims claims;try {claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();} catch (Exception e) {claims = null;}return claims;}/*** 生成令牌** @param userDetails 用户* @return 令牌*/public String generateToken(JwtUser userDetails) {Map<String, Object> claims = new HashMap<>(2);claims.put(CLAIM_KEY_USER_ACCOUNT, userDetails.getUid());claims.put(CLAIM_KEY_CREATED, new Date());return generateToken(claims);}/*** 从令牌中获取用户名** @param token 令牌* @return 用户名*/public String getUsernameFromToken(String token) {String username;try {Claims claims = getClaimsFromToken(token);username = claims.getSubject();} catch (Exception e) {username = null;}return username;}/*** 判断令牌是否过期** @param token 令牌* @return 是否过期*/public Boolean isTokenExpired(String token) {try {Claims claims = getClaimsFromToken(token);Date expiration = claims.getExpiration();return expiration.before(new Date());} catch (Exception e) {return false;}}/*** 刷新令牌** @param token 原令牌* @return 新令牌*/public String refreshToken(String token) {String refreshedToken;try {Claims claims = getClaimsFromToken(token);claims.put("created", new Date());refreshedToken = generateToken(claims);} catch (Exception e) {refreshedToken = null;}return refreshedToken;}/*** 验证令牌** @param token 令牌* @param user 用户* @return 是否有效*/public Boolean validateToken(String token, JwtUser user) {String username = getUsernameFromToken(token);return (username.equals(user.getUid()));}
}

这个代码在jdk8的环境下是正常的,不会报错的,但是在JDK11中,就有问题了

执行的时候会出现这样的异常问题

java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverterat io.jsonwebtoken.impl.Base64Codec.decode(Base64Codec.java:26) ~[jjwt-0.9.1.jar:0.9.1]at io.jsonwebtoken.impl.DefaultJwtBuilder.signWith(DefaultJwtBuilder.java:99) ~[jjwt-0.9.1.jar:0.9.1]at com.matcloud.gateway.token.JwtTokenUtil.generateToken(JwtTokenUtil.java:73) ~[classes/:na]at com.matcloud.gateway.token.JwtTokenUtil.generateToken(JwtTokenUtil.java:102) ~[classes/:na]at com.matcloud.gateway.service.impl.LoginServiceImpl.getLoginToken(LoginServiceImpl.java:110) ~[classes/:na]at com.matcloud.gateway.service.impl.LoginServiceImpl.login(LoginServiceImpl.java:52) ~[classes/:na]at com.matcloud.gateway.service.impl.LoginServiceImpl$$FastClassBySpringCGLIB$$54baea8b.invoke(<generated>) ~[classes/:na]at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.2.6.RELEASE.jar:5.2.6.RELEASE]at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:687) ~[spring-aop-5.2.6.RELEASE.jar:5.2.6.RELEASE]at com.matcloud.gateway.service.impl.LoginServiceImpl$$EnhancerBySpringCGLIB$$2f7493e.login(<generated>) ~[classes/:na]at com.matcloud.gateway.controller.TokenController.login(TokenController.java:28) ~[classes/:na]at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]at org.springframework.web.reactive.result.method.InvocableHandlerMethod.lambda$invoke$0(InvocableHandlerMethod.java:147) ~[spring-webflux-5.2.6.RELEASE.jar:5.2.6.RELEASE]at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:118) ~[reactor-core-3.3.5.RELEASE.jar:3.3.5.RELEASE]at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1755) ~[reactor-core-3.3.5.RELEASE.jar:3.3.5.RELEASE]at reactor.core.publisher.MonoZip$ZipCoordinator.signal(MonoZip.java:247) ~[reactor-core-3.3.5.RELEASE.jar:3.3.5.RELEASE]at reactor.core.publisher.MonoZip$ZipInner.onNext(MonoZip.java:329) ~[reactor-core-3.3.5.RELEASE.jar:3.3.5.RELEASE]at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onNext(MonoPeekTerminal.java:173) ~[reactor-core-3.3.5.RELEASE.jar:3.3.5.RELEASE]at reactor.core.publisher.FluxDefaultIfEmpty$DefaultIfEmptySubscriber.onNext(FluxDefaultIfEmpty.java:92) ~[reactor-core-3.3.5.RELEASE.jar:3.3.5.RELEASE]at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67) ~[reactor-core-3.3.5.RELEASE.jar:3.3.5.RELEASE]at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:73) ~[reactor-core-3.3.5.RELEASE.jar:3.3.5.RELEASE]at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1755) ~[reactor-core-3.3.5.RELEASE.jar:3.3.5.RELEASE]at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:144) ~[reactor-core-3.3.5.RELEASE.jar:3.3.5.RELEASE]at reactor.core.publisher.FluxContextStart$ContextStartSubscriber.onNext(FluxContextStart.java:96) ~[reactor-core-3.3.5.RELEASE.jar:3.3.5.RELEASE]at reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:287) ~[reactor-core-3.3.5.RELEASE.jar:3.3.5.RELEASE]at reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:330) ~[reactor-core-3.3.5.RELEASE.jar:3.3.5.RELEASE]at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1755) ~[reactor-core-3.3.5.RELEASE.jar:3.3.5.RELEASE]at reactor.core.publisher.MonoCollect$CollectSubscriber.onComplete(MonoCollect.java:152) ~[reactor-core-3.3.5.RELEASE.jar:3.3.5.RELEASE]at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:136) ~[reactor-core-3.3.5.RELEASE.jar:3.3.5.RELEASE]at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:252) ~[reactor-core-3.3.5.RELEASE.jar:3.3.5.RELEASE]at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:136) ~[reactor-core-3.3.5.RELEASE.jar:3.3.5.RELEASE]at reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:366) ~[reactor-netty-0.9.7.RELEASE.jar:0.9.7.RELEASE]at reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:367) ~[reactor-netty-0.9.7.RELEASE.jar:0.9.7.RELEASE]at reactor.netty.http.server.HttpServerOperations.onInboundNext(HttpServerOperations.java:489) ~[reactor-netty-0.9.7.RELEASE.jar:0.9.7.RELEASE]at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:96) ~[reactor-netty-0.9.7.RELEASE.jar:0.9.7.RELEASE]at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.49.Final.jar:4.1.49.Final]at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.49.Final.jar:4.1.49.Final]at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) ~[netty-transport-4.1.49.Final.jar:4.1.49.Final]at reactor.netty.http.server.HttpTrafficHandler.channelRead(HttpTrafficHandler.java:214) ~[reactor-netty-0.9.7.RELEASE.jar:0.9.7.RELEASE]at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.49.Final.jar:4.1.49.Final]at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.49.Final.jar:4.1.49.Final]at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) ~[netty-transport-4.1.49.Final.jar:4.1.49.Final]at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436) ~[netty-transport-4.1.49.Final.jar:4.1.49.Final]at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324) ~[netty-codec-4.1.49.Final.jar:4.1.49.Final]at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:296) ~[netty-codec-4.1.49.Final.jar:4.1.49.Final]at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251) ~[netty-transport-4.1.49.Final.jar:4.1.49.Final]at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.49.Final.jar:4.1.49.Final]at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.49.Final.jar:4.1.49.Final]at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) ~[netty-transport-4.1.49.Final.jar:4.1.49.Final]at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) ~[netty-transport-4.1.49.Final.jar:4.1.49.Final]at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.49.Final.jar:4.1.49.Final]at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.49.Final.jar:4.1.49.Final]at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) ~[netty-transport-4.1.49.Final.jar:4.1.49.Final]at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163) ~[netty-transport-4.1.49.Final.jar:4.1.49.Final]at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:714) ~[netty-transport-4.1.49.Final.jar:4.1.49.Final]at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:650) ~[netty-transport-4.1.49.Final.jar:4.1.49.Final]at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:576) ~[netty-transport-4.1.49.Final.jar:4.1.49.Final]at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493) ~[netty-transport-4.1.49.Final.jar:4.1.49.Final]at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989) ~[netty-common-4.1.49.Final.jar:4.1.49.Final]at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.49.Final.jar:4.1.49.Final]at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.49.Final.jar:4.1.49.Final]at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
Caused by: java.lang.ClassNotFoundException: javax.xml.bind.DatatypeConverterat java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581) ~[na:na]at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178) ~[na:na]at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521) ~[na:na]... 65 common frames omitted
第一种方法

一种是继续用旧的,然后把JDK11中删除掉的部分依赖加进来就好了,依赖还是上面那个0.9.1的依赖,或者把JDK版本降低到1.8

是jwt0.9.1的依赖,后面的是在JDK11中移除的包但jjwt0.9.1及之前的版本要用到,用以上的生成解密token方式

引入依赖

<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>
<dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>2.4.0-b180830.0359</version>
</dependency>
<dependency><groupId>com.sun.xml.bind</groupId><artifactId>jaxb-impl</artifactId><version>3.0.0-M4</version>
</dependency>
<dependency><groupId>com.sun.xml.bind</groupId><artifactId>jaxb-core</artifactId><version>3.0.0-M4</version>
</dependency>
<dependency><groupId>javax.activation</groupId><artifactId>activation</artifactId><version>1.1.1</version>
</dependency>
第二种方法

我不仅不用降级JDK,我还不想引入这些依赖,那么在pom.xml中把jjwt0.9.1的依赖删除,换成以下三个依赖

<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.2</version>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.2</version>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>0.11.2</version>
</dependency>

jjwt那个包可以看出是2018年就没有再更新了,所以猜想应该也是不支持JDK11

[外链图片转存中...(img-0wLV17Epx1594800370547)]

所以我们选择三个包导入的
在这里插入图片描述

引入之后,依然可以用原先的方法写,但是这时候会提示过时,如下
在这里插入图片描述

可以看到方法过时,如果要用这个方法来实现jwt生成,依然要引入那四个java依赖包才行,但是这里我们点进去看它的源码和它的提示

[外链图片转存中...(img-IAs7vO5a-1594800370552)]

我们点击 右上角的 download sources查看源码文档

代码如下

/*** Signs the constructed JWT using the specified algorithm with the specified key, producing a JWS.** <p>This is a convenience method: the string argument is first BASE64-decoded to a byte array and this resulting* byte array is used to invoke {@link #signWith(SignatureAlgorithm, byte[])}.</p>** <h4>Deprecation Notice: Deprecated as of 0.10.0, will be removed in the 1.0 release.</h4>** <p>This method has been deprecated because the {@code key} argument for this method can be confusing: keys for* cryptographic operations are always binary (byte arrays), and many people were confused as to how bytes were* obtained from the String argument.</p>** <p>This method always expected a String argument that was effectively the same as the result of the following* (pseudocode):</p>** <p>{@code String base64EncodedSecretKey = base64Encode(secretKeyBytes);}</p>** <p>However, a non-trivial number of JJWT users were confused by the method signature and attempted to* use raw password strings as the key argument - for example {@code signWith(HS256, myPassword)} - which is* almost always incorrect for cryptographic hashes and can produce erroneous or insecure results.</p>** <p>See this* <a href="https://stackoverflow.com/questions/40252903/static-secret-as-byte-key-or-string/40274325#40274325">* StackOverflow answer</a> explaining why raw (non-base64-encoded) strings are almost always incorrect for* signature operations.</p>** <p>To perform the correct logic with base64EncodedSecretKey strings with JJWT >= 0.10.0, you may do this:* <pre><code>* byte[] keyBytes = {@link Decoders Decoders}.{@link Decoders#BASE64 BASE64}.{@link Decoder#decode(Object) decode(base64EncodedSecretKey)};* Key key = {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(keyBytes)};* jwtBuilder.signWith(key); //or {@link #signWith(Key, SignatureAlgorithm)}* </code></pre>* </p>** <p>This method will be removed in the 1.0 release.</p>** @param alg                    the JWS algorithm to use to digitally sign the JWT, thereby producing a JWS.* @param base64EncodedSecretKey the BASE64-encoded algorithm-specific signing key to use to digitally sign the*                               JWT.* @return the builder for method chaining.* @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification as*                             described by {@link SignatureAlgorithm#forSigningKey(Key)}.* @deprecated as of 0.10.0: use {@link #signWith(Key)} or {@link #signWith(Key, SignatureAlgorithm)} instead.  This* method will be removed in the 1.0 release.*/
@Deprecated
JwtBuilder signWith(SignatureAlgorithm alg, String base64EncodedSecretKey) throws InvalidKeyException;

翻译一下大概是这样




```bash
io.jsonwebtoken.JwtBuilder @Deprecated 
JwtBuilder signWith(SignatureAlgorithm alg,String base64EncodedSecretKey)
throws InvalidKeyException
标志使用指定的算法具有指定键,产生JWS构建JWT。
这是一个方便的方法:将字符串参数被首先BASE64解码为字节阵列和由此产生的字节数组用于调用signWith(SignatureAlgorithm, byte[])
弃用注意:弃用的0.10.0,将在1.0版本中删除。
这种方法已经被废弃了,因为key此方法的参数可以是混乱:为加密操作键始终二进制(字节数组),和许多人困惑,字节是如何从字符串参数获得。
此方法始终预期有效地是一样的下面(伪代码)的结果的字符串参数:
String base64EncodedSecretKey = base64Encode(secretKeyBytes);
然而,JJWT用户的一个非平凡数是由方法签名混淆,并试图使用原始密码字符串作为密钥参数-例如signWith(HS256, myPassword) -这是几乎总是不正确的密码散列,并且能够产生错误的或不安全的结果。
看到这个StackOverflow的答案  ,解释为什么生(非base64编码)的字符串几乎都是不正确的签名操作。
与base64EncodedSecretKey字符串与JJWT> = 0.10.0执行正确的逻辑,你可以这样做:

/**

To perform the correct logic with base64EncodedSecretKey strings with JJWT >= 0.10.0, you may do this:

  • 
    
  • byte[] keyBytes = {@link Decoders Decoders}.{@link Decoders#BASE64 BASE64}.{@link Decoder#decode(Object) decode(base64EncodedSecretKey)};
  • Key key = {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(keyBytes)};
  • jwtBuilder.signWith(key); //or {@link #signWith(Key, SignatureAlgorithm)}
  • */

这里它给出了推荐做法,就是最下面的注释翻译成代码

     byte[] keyBytes = Decoders.BASE64.decode(base64EncodedSecretKey);Key key = Keys.hmacShaKeyFor(keyBytes);jwtBuilder.signWith(key); //or signWith(Key, SignatureAlgorithm)

可以看到无论是signWith(key); //还是 signWith(Key, SignatureAlgorithm)传的参数都不再是原先的String base64EncodedSecretKey或者byte[] secretKey,而是统一使用了一个Key接口的参数,

话不多说,开搞

当我们不替换的时候,直接用原来的代码,奇怪的是

[外链图片转存中...(img-1fEECQWF-1594800370553)]

我把jjtwt换成0.9.1把jdk去除的依赖都加上,byte[] bytes = TextCodec.BASE64.decode(base64EncodedSecretKey);就可以解析,但是换成jjwt0.10.2,就会报异常,说他有非法字符串“-”

Exception in thread "main" io.jsonwebtoken.io.DecodingException: Illegal base64 character: '-'at io.jsonwebtoken.io.Base64.ctoi(Base64.java:221)at io.jsonwebtoken.io.Base64.decodeFast(Base64.java:270)at io.jsonwebtoken.io.Base64Decoder.decode(Base64Decoder.java:36)at io.jsonwebtoken.io.Base64Decoder.decode(Base64Decoder.java:23)at io.jsonwebtoken.io.ExceptionPropagatingDecoder.decode(ExceptionPropagatingDecoder.java:36)at io.jsonwebtoken.impl.Base64Codec.decode(Base64Codec.java:34)at com.matcloud.gateway.SingleTests.main(SingleTests.java:22)

然后换成0.9.1它就可以decode,我试过jdk自带的bash64及其他工具,只有hutool包下的base64可以解析该字符串,但是解析结果与其不一致,再没有找到可以解析这个字符串其他工具包

也就是说原来0.9.1的TextCode.BASE64.decode直接解密我之前的secret字符串没有问题,但是在0.10.2中却报异常了

于是查看源码,发现了两个版本的不同

0.11.2

package io.jsonwebtoken.impl;import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.Encoders;/*** @deprecated since 0.10.0 - will be removed before 1.0.0. Use {@code io.jsonwebtoken.io.Encoders#BASE64}* or {@code io.jsonwebtoken.io.Decoders#BASE64}*/
@Deprecated
public class Base64Codec extends AbstractTextCodec {public String encode(byte[] data) {return Encoders.BASE64.encode(data);}@Overridepublic byte[] decode(String encoded) {return Decoders.BASE64.decode(encoded);}
}

可以看到它用了Decoders来处理

0.9.1版本底层则是

package io.jsonwebtoken.impl;public class Base64Codec extends AbstractTextCodec {public String encode(byte[] data) {return javax.xml.bind.DatatypeConverter.printBase64Binary(data);}@Overridepublic byte[] decode(String encoded) {return javax.xml.bind.DatatypeConverter.parseBase64Binary(encoded);}
}

用了javax.xml.bind.DatatypeConverter.parseBase64Binary(encoded),他在JDK11中已不存在,且这两个方法不兼容,同一个字符串,在javax.xml.bind.DatatypeConverter.parseBase64Binary(encoded)上可以解析,但是在Decoders.BASE64.decode(encoded)中则不一定,很巧的是我的字符串就不能通用解析,字符串中包含“-”号,只能在jjwt0.9.1以下版本的TextCodec中解析成功,hutool也可以解析,但是结果都不一样,不过这里不用太纠结,我们直接用它推荐的方法弄,

随机生成一个secret就行,不要包含特殊符号,然后可以看到解密也已过时,注意这里不一定要加Base64.encode,如果你的字符串有你的加密解密体系,就用你自己的就好[外链图片转存中...(img-SB5yvXhM-1594800370555)]

进源码查看

/*** Sets the signing key used to verify any discovered JWS digital signature.  If the specified JWT string is not* a JWS (no signature), this key is not used.** <p>Note that this key <em>MUST</em> be a valid key for the signature algorithm found in the JWT header* (as the {@code alg} header parameter).</p>** <p>This method overwrites any previously set key.</p>** <p>This is a convenience method: the string argument is first BASE64-decoded to a byte array and this resulting* byte array is used to invoke {@link #setSigningKey(byte[])}.</p>** <h4>Deprecation Notice: Deprecated as of 0.10.0, will be removed in 1.0.0</h4>** <p>This method has been deprecated because the {@code key} argument for this method can be confusing: keys for* cryptographic operations are always binary (byte arrays), and many people were confused as to how bytes were* obtained from the String argument.</p>** <p>This method always expected a String argument that was effectively the same as the result of the following* (pseudocode):</p>** <p>{@code String base64EncodedSecretKey = base64Encode(secretKeyBytes);}</p>** <p>However, a non-trivial number of JJWT users were confused by the method signature and attempted to* use raw password strings as the key argument - for example {@code setSigningKey(myPassword)} - which is* almost always incorrect for cryptographic hashes and can produce erroneous or insecure results.</p>** <p>See this* <a href="https://stackoverflow.com/questions/40252903/static-secret-as-byte-key-or-string/40274325#40274325">* StackOverflow answer</a> explaining why raw (non-base64-encoded) strings are almost always incorrect for* signature operations.</p>** <p>Finally, please use the {@link #setSigningKey(Key) setSigningKey(Key)} instead, as this method and the* {@code byte[]} variant will be removed before the 1.0.0 release.</p>** @param base64EncodedSecretKey the BASE64-encoded algorithm-specific signature verification key to use to validate*                               any discovered JWS digital signature.* @return the parser for method chaining.* @deprecated see {@link JwtParserBuilder#setSigningKey(String)}.* To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an* immutable JwtParser.* <p><b>NOTE: this method will be removed before version 1.0</b>*/
@Deprecated
JwtParser setSigningKey(String base64EncodedSecretKey);

可以查看到该方法已不被推荐,

[外链图片转存中...(img-DFxL9MHs-1594800370556)]

可以看到它推荐我们使用JwtPaserBuilder替代它,不多说搞起,替换完之后

[外链图片转存中...(img-hWxCFKhB-1594800370557)]

嗯?它报错了,点开看了好多也没找到

parseClaimsJws(token).getBody()

应该追加在哪,忽然灵机一动,builder。那应该还有个.build(),果不其然,build之后就可以直接在JwtParser后配置获取数据了,最终代码如下

claims = Jwts.parserBuilder().setSigningKey(secret).build().parseClaimsJws(token).getBody();

因为我最后换成了所有Base64都能合法解密的字符串,所以我就把原来生成jwt的方法代码–

/*** 从数据声明生成令牌** @param claims 数据声明* @return 令牌*/
private String generateToken(Map<String, Object> claims) {String encode = Base64.encode(secret);//原本这里secret在jjwt0.9.1之后的版本中直接decode失败,在jjwt0.9.1及之前的版本可以成功解密,所以这里我相当于又给它加密解密,byte[] keyBytes = Decoders.BASE64.decode(encode); //这里先转码成base64又转回来,0.9.1之后只推荐使用key的形式sign,所以上面那个解密jwt会有Base64.encode(secret),因为实际上我的加密secret变成了现在的加密后的secretKey key = Keys.hmacShaKeyFor(keyBytes);return Jwts.builder().setClaims(claims).signWith(key).compact();
}

改成了这样

    /*** 从数据声明生成令牌** @param claims 数据声明* @return 令牌*/private String generateToken(Map<String, Object> claims) {
//        String encode = Base64.encode(secret); //现在我新生成的secret字符合法了,并且可以解密,不用再加密后当secret用了byte[] keyBytes = Decoders.BASE64.decode(secret);Key key = Keys.hmacShaKeyFor(keyBytes);return Jwts.builder().setClaims(claims).signWith(key).compact();}

对应解析jwt的代码

/*** 从令牌中获取数据声明** @param token 令牌* @return 数据声明*/
private Claims getClaimsFromToken(String token) {Claims claims;try {claims = Jwts.parserBuilder().setSigningKey(secret).build().parseClaimsJws(token).getBody();} catch (Exception e) {claims = null;}return claims;
}

注意,如果使用jjwt高于0.9.1的版本,其自带的decode与0.9.1及以下版本可能有不兼容的情况

这篇关于JDK11,8引入不同版本的jjwt异常问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/734968

相关文章

无人叉车3d激光slam多房间建图定位异常处理方案-墙体画线地图切分方案

墙体画线地图切分方案 针对问题:墙体两侧特征混淆误匹配,导致建图和定位偏差,表现为过门跳变、外月台走歪等 ·解决思路:预期的根治方案IGICP需要较长时间完成上线,先使用切分地图的工程化方案,即墙体两侧切分为不同地图,在某一侧只使用该侧地图进行定位 方案思路 切分原理:切分地图基于关键帧位置,而非点云。 理论基础:光照是直线的,一帧点云必定只能照射到墙的一侧,无法同时照到两侧实践考虑:关

好题——hdu2522(小数问题:求1/n的第一个循环节)

好喜欢这题,第一次做小数问题,一开始真心没思路,然后参考了网上的一些资料。 知识点***********************************无限不循环小数即无理数,不能写作两整数之比*****************************(一开始没想到,小学没学好) 此题1/n肯定是一个有限循环小数,了解这些后就能做此题了。 按照除法的机制,用一个函数表示出来就可以了,代码如下

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

2. c#从不同cs的文件调用函数

1.文件目录如下: 2. Program.cs文件的主函数如下 using System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;using System.Windows.Forms;namespace datasAnalysis{internal static

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

购买磨轮平衡机时应该注意什么问题和技巧

在购买磨轮平衡机时,您应该注意以下几个关键点: 平衡精度 平衡精度是衡量平衡机性能的核心指标,直接影响到不平衡量的检测与校准的准确性,从而决定磨轮的振动和噪声水平。高精度的平衡机能显著减少振动和噪声,提高磨削加工的精度。 转速范围 宽广的转速范围意味着平衡机能够处理更多种类的磨轮,适应不同的工作条件和规格要求。 振动监测能力 振动监测能力是评估平衡机性能的重要因素。通过传感器实时监

uva 10061 How many zero's and how many digits ?(不同进制阶乘末尾几个0)+poj 1401

题意是求在base进制下的 n!的结果有几位数,末尾有几个0。 想起刚开始的时候做的一道10进制下的n阶乘末尾有几个零,以及之前有做过的一道n阶乘的位数。 当时都是在10进制下的。 10进制下的做法是: 1. n阶位数:直接 lg(n!)就是得数的位数。 2. n阶末尾0的个数:由于2 * 5 将会在得数中以0的形式存在,所以计算2或者计算5,由于因子中出现5必然出现2,所以直接一

缓存雪崩问题

缓存雪崩是缓存中大量key失效后当高并发到来时导致大量请求到数据库,瞬间耗尽数据库资源,导致数据库无法使用。 解决方案: 1、使用锁进行控制 2、对同一类型信息的key设置不同的过期时间 3、缓存预热 1. 什么是缓存雪崩 缓存雪崩是指在短时间内,大量缓存数据同时失效,导致所有请求直接涌向数据库,瞬间增加数据库的负载压力,可能导致数据库性能下降甚至崩溃。这种情况往往发生在缓存中大量 k

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)