荐 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;
}
@JsonIgnore
public String getPassword() {
return password;
}
@JsonIgnore
public boolean isAccountNonExpired() {
return true;
}
@JsonIgnore
public boolean isAccountNonLocked() {
return state == 1 || state == 3 || state == 4 || state == 5;
}
@JsonIgnore
public boolean isCredentialsNonExpired() {
return true;
}
@JsonIgnore
public 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/DatatypeConverter
at 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.DatatypeConverter
at 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
所以我们选择三个包导入的
引入之后,依然可以用原先的方法写,但是这时候会提示过时,如下
可以看到方法过时,如果要用这个方法来实现jwt生成,依然要引入那四个java依赖包才行,但是这里我们点进去看它的源码和它的提示
我们点击 右上角的 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://*.com/questions/40252903/static-secret-as-byte-key-or-string/40274325#40274325">
* * 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) -这是几乎总是不正确的密码散列,并且能够产生错误的或不安全的结果。
看到这个*的答案 ,解释为什么生(非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接口的参数,
话不多说,开搞
当我们不替换的时候,直接用原来的代码,奇怪的是
我把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);
}
@Override
public 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);
}
@Override
public 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,如果你的字符串有你的加密解密体系,就用你自己的就好
进源码查看
/**
* 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://*.com/questions/40252903/static-secret-as-byte-key-or-string/40274325#40274325">
* * 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);
可以查看到该方法已不被推荐,
可以看到它推荐我们使用JwtPaserBuilder替代它,不多说搞起,替换完之后
嗯?它报错了,点开看了好多也没找到
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变成了现在的加密后的secret
Key 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及以下版本可能有不兼容的情况
本文地址:https://blog.csdn.net/u010748421/article/details/107363925