当前位置: 移动技术网 > IT编程>开发语言>Java > 荐 JDK11,8引入不同版本的jjwt异常问题

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

2020年07月17日  | 移动技术网IT编程  | 我要评论
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&g

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

[外链图片转存中...(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);
    }

    @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,如果你的字符串有你的加密解密体系,就用你自己的就好[外链图片转存中...(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变成了现在的加密后的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

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网