欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-12-20 13:35:50
JJWT在JDK11,8及不同版本的处理问题问题原先在旧的项目中,用的是SpringCloudGateway2.0.4,对应的maven依赖是spring-cloud-starter-gateway:2.0.4.RELEASE,springboot的版本是2.0.6.RELEASE,jwt则直接是一个依赖全部引进来,如下所示 io.jsonwebtoken

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

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

所以我们选择三个包导入的
荐
                                                        JDK11,8引入不同版本的jjwt异常问题

引入之后,依然可以用原先的方法写,但是这时候会提示过时,如下
荐
                                                        JDK11,8引入不同版本的jjwt异常问题

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

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

我们点击 右上角的 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接口的参数,

话不多说,开搞

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

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

我把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,如果你的字符串有你的加密解密体系,就用你自己的就好荐
                                                        JDK11,8引入不同版本的jjwt异常问题

进源码查看

/**
 * 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);

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

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

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

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

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

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