本文主要是介绍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
所以我们选择三个包导入的
引入之后,依然可以用原先的方法写,但是这时候会提示过时,如下
可以看到方法过时,如果要用这个方法来实现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://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接口的参数,
话不多说,开搞
当我们不替换的时候,直接用原来的代码,奇怪的是
我把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,如果你的字符串有你的加密解密体系,就用你自己的就好
进源码查看
/*** 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);
可以查看到该方法已不被推荐,
可以看到它推荐我们使用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变成了现在的加密后的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异常问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!