【问题标题】:Unexpected "InvalidJwtSignatureException: JWT rejected due to invalid signature"意外的“InvalidJwtSignatureException:JWT 由于签名无效而被拒绝”
【发布时间】:2021-05-07 14:26:29
【问题描述】:

我有一个看起来像这样的 JWT(我不得不隐藏一些值):

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJ4eHgiLCJpc3MiOiJ4eHgiLCJpYXQiOjE2MTIzNDkwMTEsIm5iZiI6MCwiZXhwIjoxNjEyMzUyNjExLCJhdXRoX3RpbWUiOjE2MTEwNDU5MjgsIm5vbmNlIjoieHh4Iiwic3ViIjoieHh4IiwidXBuIjoieHh4IiwidW5pcXVlX25hbWUiOiJ4eHgiLCJwd2RfdXJsIjoieHh4IiwicHdkX2V4cCI6Inh4eCIsInNpZCI6Inh4eCIsImp0aSI6ImZmYWQ0NjM1LTU3MmItNGUyYi04ZGRhLTAxNmEzNDRlYzY4ZiJ9.nW5xTs6IbEkIFTZ_9PJZBpZAHXqG2HeU6y0XJwmQZiM

或者简单地说:

{
 "typ": "JWT",
 "alg": "RS256",
 "x5t": "8Q3reRBv6jj6FyxBo5phA1yKzYg",
 "kid": "8Q3reRBv6jj6FyxBo5phA1yKzYg"
}

{
 "aud": "xxx",
 "iss": "xxx",
 "iat": 0,
 "nbf": 0,
 "exp": 1611049528,
 "auth_time": 1611045928,
 "nonce": "xxx",
 "sub": "xxx",
 "upn": "xxx",
 "unique_name": "xxx",
 "pwd_url": "xxx",
 "pwd_exp": "xxx",
 "sid": "xxx"
}

这是我为验证它而编写的代码(从网站中的示例开始)。

   @SneakyThrows
    public boolean isValid(String extractedToken) {
        log.info("Validating JWT");

        // Generate an RSA key pair, which will be used for signing and verification of the JWT, wrapped in a JWK
        RsaJsonWebKey rsaJsonWebKey = RsaJwkGenerator.generateJwk(2048);

        // Give the JWK a Key ID (kid), which is just the polite thing to do
        rsaJsonWebKey.setKeyId("8Q3reRBv6jj6FyxBo5phA1yKzYg");
        rsaJsonWebKey.setX509CertificateSha256Thumbprint("8Q3reRBv6jj6FyxBo5phA1yKzYg");

        // Use JwtConsumerBuilder to construct an appropriate JwtConsumer, which will
        // be used to validate and process the JWT.
        // The specific validation requirements for a JWT are context dependent, however,
        // it typically advisable to require a (reasonable) expiration time, a trusted issuer, and
        // and audience that identifies your system as the intended recipient.
        // If the JWT is encrypted too, you need only provide a decryption key or
        // decryption key resolver to the builder.
        JwtConsumer jwtConsumer = new JwtConsumerBuilder()
                .setRequireExpirationTime() // the JWT must have an expiration time
                .setAllowedClockSkewInSeconds(30) // allow some leeway in validating time based claims to account for clock skew
                .setVerificationKey(rsaJsonWebKey.getKey()) // verify the signature with the public key
                .setJwsAlgorithmConstraints( // only allow the expected signature algorithm(s) in the given context
                        AlgorithmConstraints.ConstraintType.PERMIT, AlgorithmIdentifiers.RSA_USING_SHA256) // which is only RS256 here
                .build(); // create the JwtConsumer instance

        try
        {
            //  Validate the JWT and process it to the Claims
            JwtClaims jwtClaims = jwtConsumer.processToClaims(extractedToken);
            System.out.println("JWT validation succeeded! " + jwtClaims);
            log.info("JTW validated");
            return true;
        }
        catch (InvalidJwtException e)
        {
            // InvalidJwtException will be thrown, if the JWT failed processing or validation in anyway.
            // Hopefully with meaningful explanations(s) about what went wrong.
            System.out.println("Invalid JWT! " + e);

            // Programmatic access to (some) specific reasons for JWT invalidity is also possible
            // should you want different error handling behavior for certain conditions.

            // Whether or not the JWT has expired being one common reason for invalidity
            if (e.hasExpired())
            {
                System.out.println("JWT expired at " + e.getJwtContext().getJwtClaims().getExpirationTime());
            }

            // Or maybe the audience was invalid
            if (e.hasErrorCode(ErrorCodes.AUDIENCE_INVALID))
            {
                System.out.println("JWT had wrong audience: " + e.getJwtContext().getJwtClaims().getAudience());
            }
        }

        log.info("JTW validated");
        return false;
    }

我的目标是目前仅验证签名而不验证其他密钥对值。

但是,当我运行代码时,我得到:

Invalid JWT! org.jose4j.jwt.consumer.InvalidJwtSignatureException: JWT rejected due to invalid signature. Additional details: [[9] Invalid JWS Signature: JsonWebSignature{"typ":"JWT","alg":"RS256","x5t":"8Q3reRBv6jj6FyxBo5phA1yKzYg","kid":"8Q3reRBv6jj6FyxBo5phA1yKzYg"}->eyJ0eXAiOi.....]

而“->”右侧的token确实是我的token。

所以我怀疑我没有正确设置 JWTConsumer,但我看不到错误在哪里。

工作解决方案:

@SneakyThrows
public boolean isValid(String extractedToken) {
    log.info("Validating JWT");

    String pem = "MIIBIj........DAQAB";

    X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(Base64.getMimeDecoder().decode(pem));
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    PublicKey publicKey = keyFactory.generatePublic(pubKeySpec);

    // Use JwtConsumerBuilder to construct an appropriate JwtConsumer, which will
    // be used to validate and process the JWT.
    // The specific validation requirements for a JWT are context dependent, however,
    // it typically advisable to require a (reasonable) expiration time, a trusted issuer, and
    // and audience that identifies your system as the intended recipient.
    // If the JWT is encrypted too, you need only provide a decryption key or
    // decryption key resolver to the builder.
    JwtConsumer jwtConsumer = new JwtConsumerBuilder()
            .setVerificationKey(publicKey) // verify the signature with the public key
            .setJwsAlgorithmConstraints( // only allow the expected signature algorithm(s) in the given context
                    AlgorithmConstraints.ConstraintType.PERMIT, AlgorithmIdentifiers.RSA_USING_SHA256) // which is only RS256 here
            .setSkipAllDefaultValidators()
            .build(); // create the JwtConsumer instance

    try
    {
        //  Validate the JWT and process it to the Claims
        JwtClaims jwtClaims = jwtConsumer.processToClaims(extractedToken);
        System.out.println("JWT validation succeeded! " + jwtClaims);
        log.info("JTW validated");
        return true;
    }
    catch (InvalidJwtException e)
    {
        // InvalidJwtException will be thrown, if the JWT failed processing or validation in anyway.
        // Hopefully with meaningful explanations(s) about what went wrong.
        System.out.println("Invalid JWT! " + e);

        // Programmatic access to (some) specific reasons for JWT invalidity is also possible
        // should you want different error handling behavior for certain conditions.

        // Whether or not the JWT has expired being one common reason for invalidity
        if (e.hasExpired())
        {
            System.out.println("JWT expired at " + e.getJwtContext().getJwtClaims().getExpirationTime());
        }

        // Or maybe the audience was invalid
        if (e.hasErrorCode(ErrorCodes.AUDIENCE_INVALID))
        {
            System.out.println("JWT had wrong audience: " + e.getJwtContext().getJwtClaims().getAudience());
        }
    }

log.info("JTW validated");
return false;

}

【问题讨论】:

  • 我相信密钥必须用base64编码..
  • RsaJwkGenerator 的目的是生成一个随机的 RSA 密钥对,因此如果您想取消验证 JWT 令牌。您必须使用 RSA 私钥签署此令牌(请参阅此处 bitbucket.org/b_c/jose4j/wiki/JWT%20Examples)密钥 ID 和指纹仅用于使用 JWK 将正确的公钥链接到令牌
  • @aran 我尝试将字符串“8Q3reRBv6jj6FyxBo5phA1yKzYg”编码为 base64 和 base64url 并将结果粘贴到代码中,但它不起作用。我误会你的评论了吗?
  • @vmargerin 我正是使用该代码作为示例。你能否更具体地告诉我我应该改变什么?

标签: java jose4j


【解决方案1】:

extractToken 签名来自与此处生成的不同的 RSA 对:

RsaJsonWebKey rsaJsonWebKey = RsaJwkGenerator.generateJwk(2048);

所以当你这样做时:

JwtConsumer jwtConsumer = new JwtConsumerBuilder()
            .setRequireExpirationTime() // the JWT must have an expiration time
            .setAllowedClockSkewInSeconds(30) // allow some leeway in validating time based claims to account for clock skew
            .setVerificationKey(rsaJsonWebKey.getKey()) // verify the signature with the public key
            .setJwsAlgorithmConstraints( // only allow the expected signature algorithm(s) in the given context
                    AlgorithmConstraints.ConstraintType.PERMIT, AlgorithmIdentifiers.RSA_USING_SHA256) // which is only RS256 here
            .build();

然后:

JwtClaims jwtClaims = jwtConsumer.processToClaims(extractedToken);

您正在根据另一个公钥(不是最初使用的那个)验证您的令牌

为了使 JWT 令牌的签名生效,你必须保留原始的 RSA 对,然后将公钥放入 JWTConsumer 中,如下所示:

.setVerificationKey(originalPublicKey)

【讨论】:

  • 您好 Vmargerin,感谢您的回答!我将公钥作为字符串,但代码无法编译。如何正确将其转换为密钥?
  • 这取决于String key是如何编码的。这里已经提到了这个问题:bitbucket.org/b_c/jose4j/issues/13/…
  • 嘿,感谢您的宝贵提示。在我将此标记为已解决之前还有一点。现在我得到两个错误:过期和错误的观众。我没有在我的代码中为这两个检查设置任何要求。如果我使用 .setSkipAllDefaultValidators(),我是否也跳过了签名的验证?
  • 供参考:我问这个问题是为了了解我的代码是否真的在工作,或者我正在跳过签名检查
  • 如果您不期望特定的受众,您可以使用 setSkipDefaultAudienceValidation()。但通常建议验证受众是否可行(使用 setExpectedAudience(audience))。我不完全确定,但 setSkipAllDefaultValidators 应该删除声明验证(不是签名)javadoc.io/static/org.bitbucket.b_c/jose4j/0.6.2/org/jose4j/jwt/…
猜你喜欢
  • 2015-11-01
  • 2012-10-28
  • 1970-01-01
  • 1970-01-01
  • 2021-04-23
  • 2013-09-27
  • 2018-02-23
  • 2021-09-16
  • 1970-01-01
相关资源
最近更新 更多