docs: public static JwtClaims verifyJwt(String jwt, boolean ignoreExpiry, boolean isToken) throws InvalidJwtException, ExpiredTokenException {
          JwtClaims claims;
  
          if(Boolean.TRUE.equals(enableJwtCache)) {
              claims = cache.getIfPresent(jwt);
              if(claims != null) {
                  if(!ignoreExpiry) {
                      try {
                          // if using our own client module, the jwt token should be renewed automatically
                          // and it will never expired here. However, we need to handle other clients.
                          if ((NumericDate.now().getValue() - secondsOfAllowedClockSkew) >= claims.getExpirationTime().getValue())
                          {
                              logger.info("Cached jwt token is expired!");
                              throw new ExpiredTokenException("Token is expired");
                          }
                      } catch (MalformedClaimException e) {
                          // This is cached token and it is impossible to have this exception
                          logger.error("MalformedClaimException:", e);
                      }
                  }
                  // this claims object is signature verified already
                  return claims;
              }
          }
  
          JwtConsumer consumer = new JwtConsumerBuilder()
                  .setSkipAllValidators()
                  .setDisableRequireSignature()
                  .setSkipSignatureVerification()
                  .build();
  
          JwtContext jwtContext = consumer.process(jwt);
          claims = jwtContext.getJwtClaims();
          JsonWebStructure structure = jwtContext.getJoseObjects().get(0);
          // need this kid to load public key certificate for signature verification
          String kid = structure.getKeyIdHeaderValue();
  
          // so we do expiration check here manually as we have the claim already for kid
          // if ignoreExpiry is false, verify expiration of the token
          if(!ignoreExpiry) {
              try {
                  if ((NumericDate.now().getValue() - secondsOfAllowedClockSkew) >= claims.getExpirationTime().getValue())
                  {
                      logger.info("jwt token is expired!");
                      throw new ExpiredTokenException("Token is expired");
                  }
              } catch (MalformedClaimException e) {
                  logger.error("MalformedClaimException:", e);
                  throw new InvalidJwtException("MalformedClaimException", new ErrorCodeValidator.Error(ErrorCodes.MALFORMED_CLAIM, "Invalid ExpirationTime Format"), e, jwtContext);
              }
          }
  
          // get the public key certificate from the cache that is loaded from security.yml if it is not there,
          // go to OAuth2 server /oauth2/key endpoint to get the public key certificate with kid as parameter.
          X509Certificate certificate = certMap == null? null : certMap.get(kid);
          if(certificate == null) {
              certificate = isToken? getCertForToken(kid) : getCertForSign(kid);
              if(certMap == null) certMap = new HashMap<>();  // null if bootstrapFromKeyService is true
              certMap.put(kid, certificate);
          }
          X509VerificationKeyResolver x509VerificationKeyResolver = new X509VerificationKeyResolver(certificate);
  
          x509VerificationKeyResolver.setTryAllOnNoThumbHeader(true);
          consumer = new JwtConsumerBuilder()
                  .setRequireExpirationTime()
                  .setAllowedClockSkewInSeconds(315360000) // use seconds of 10 years to skip expiration validation as we need skip it in some cases.
                  .setSkipDefaultAudienceValidation()
                  .setVerificationKeyResolver(x509VerificationKeyResolver)
                  .build();
  
          // Validate the JWT and process it to the Claims
          jwtContext = consumer.process(jwt);
          claims = jwtContext.getJwtClaims();
          if(Boolean.TRUE.equals(enableJwtCache)) {
              cache.put(jwt, claims);
          }
          return claims;
      }