java 启用加密和签名时 JWT 中的解析器异常

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/30888180/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 17:47:05  来源:igfitidea点击:

Parser exception in JWT when encryption and signing is enabled

javajsonjwtjose4j

提问by Ashok

I'm new to JWT, learning through standalone code to understand JWT API's. Below code sign and encrypt JWT token from sender's end and it get validated at receiver's end.

我是 JWT 的新手,通过独立代码学习以了解 JWT API。下面的代码从发送端对 JWT 令牌进行签名和加密,并在接收端进行验证。

Library: JOSE 0.4.1

库:JOSE 0.4.1

package com.one00bytes.jwt;

public class JWTSignEncryption {

public static void main(String[] args) throws Exception {

    /***************************SENDER'S END ***********************************/

    JwtClaims claims = new JwtClaims();
    claims.setAudience("Admins");
    claims.setIssuer("CA");
    claims.setSubject("users");
    claims.setClaim("email", "[email protected]");
    claims.setClaim("Country", "Antartica");
    System.out.println(claims.toJson());

    //SIGNING
    RsaJsonWebKey jsonSignKey = RsaJwkGenerator.generateJwk(2048);
    JsonWebSignature jws = new JsonWebSignature();
    jws.setKey(jsonSignKey.getPrivateKey());
    jws.setPayload(claims.toJson());
    jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA512);
    String signedJwt = jws.getCompactSerialization();
    System.out.println("Signed ::" + signedJwt);


    //ENCRYPTING
    RsaJsonWebKey keyEncrypt = RsaJwkGenerator.generateJwk(2048);
    KeyGenerator keyGen = KeyGenerator.getInstance("AES");
    keyGen.init(256);
    SecretKey contentEncryptKey = keyGen.generateKey();

    JsonWebEncryption jwe = new JsonWebEncryption();
    jwe.setKey(keyEncrypt.getPublicKey());
    jwe.setPayload(signedJwt);
    jwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.RSA_OAEP_256);
    jwe.setContentEncryptionKey(contentEncryptKey.getEncoded());
    jwe.setEncryptionMethodHeaderParameter(ContentEncryptionAlgorithmIdentifiers.AES_256_GCM);
    SecureRandom iv = SecureRandom.getInstance("SHA1PRNG");
    jwe.setIv(iv.generateSeed(32));
    String encryptedJwt = jwe.getCompactSerialization();
    System.out.println("Encrypted ::" + encryptedJwt);


    /***************************RECEIVER'S END ***********************************/ 

    JwtConsumer consumer = new JwtConsumerBuilder()
                            .setExpectedAudience("Admins")
                            .setExpectedIssuer("CA")
                            .setRequireSubject()
                            .setDecryptionKey(keyEncrypt.getPrivateKey())
                            .setVerificationKey(jsonSignKey.getPublicKey())
                            .build();
    JwtClaims receivedClaims = consumer.processToClaims(encryptedJwt);
    System.out.println("SUCESS :: JWT Validation :: " + receivedClaims);

}

}

}

Observing below exception when running this program:

运行此程序时观察到以下异常:

Exception in thread "main" org.jose4j.jwt.consumer.InvalidJwtException: Unable to parse JWT Claim Set JSON: eyJhbGciOiJSUzUxMiJ9.eyJhdWQiOiJBZG1pbnMiLCJpc3MiOiJDQSIsInN1YiI6InVzZXJzIiwiaWF0IjoxNDM0NTM0MDgxLCJleHAiOjE0MzQ1MzQ2ODEsImp0aSI6IjJxUUpuMDVGY3RrLWF1VG1vVktuWXciLCJuYmYiOjE0MzQ1MzM5NjEsImVtYWlsIjoidXNlcnNAMTAwYnl0ZXMuY29tIiwiQ291bnRyeSI6IkFudGFydGljYSIsImhvYmJpZXMiOlsiQmxvZ2dpbmciLCJQbGF5aW5nIGNhcmRzIiwiR2FtZXMiXX0.soY_5Hbam569I-CnUW1F4GWdaqprh-XAOtAMOcb7zZSiRcIhXYUdJjEslrDbwphAP135SvmoXO4nVaVmo-d8oWREFYUeXEDzHbrqHNp7pp5pH6hGTJ5C4uE1UVzZ4bis3g_KEgZvEn31NnV4RcU_oRn2Q4inkrTlYKY-juEtCmpPQ0sSP4GiDbwVIfCj-kxZsKh_i9n28SSK890K3DIGiFWOUDwrnY4Yfr1UffsUS9ovyhtqrOcN4YsJR4XzGPaLehlR-qD7eOdAdmVb8RDtGKufNuCd7Q9OFfeKzBmGITHsvd6IPVYLLCfSCzO6PqQSIzkupl5D6HqoOqID8JZLxA
    at org.jose4j.jwt.JwtClaims.<init>(JwtClaims.java:50)
    at org.jose4j.jwt.JwtClaims.parse(JwtClaims.java:56)
    at org.jose4j.jwt.consumer.JwtConsumer.process(JwtConsumer.java:267)
    at org.jose4j.jwt.consumer.JwtConsumer.processToClaims(JwtConsumer.java:115)
    at com.one00bytes.jwt.JWTSignEncryption.main(JWTSignEncryption.java:76)
Caused by: org.jose4j.lang.JoseException: Parsing error: org.jose4j.json.internal.json_simple.parser.ParseException: Unexpected character (e) at position 0.
    at org.jose4j.json.JsonUtil.parseJson(JsonUtil.java:66)
    at org.jose4j.jwt.JwtClaims.<init>(JwtClaims.java:45)
    ... 4 more
Caused by: org.jose4j.json.internal.json_simple.parser.ParseException: Unexpected character (e) at position 0.
    at org.jose4j.json.internal.json_simple.parser.Yylex.yylex(Yylex.java:612)
    at org.jose4j.json.internal.json_simple.parser.JSONParser.nextToken(JSONParser.java:269)
    at org.jose4j.json.internal.json_simple.parser.JSONParser.parse(JSONParser.java:118)
    at org.jose4j.json.internal.json_simple.parser.JSONParser.parse(JSONParser.java:81)
    at org.jose4j.json.JsonUtil.parseJson(JsonUtil.java:62)
    ... 5 more

Signed JWT

签署的 JWT

eyJhbGciOiJSUzUxMiJ9.eyJhdWQiOiJBZG1pbnMiLCJpc3MiOiJDQSIsInN1YiI6InVzZXJzIiwiZW1haWwiOiJ1c2Vyc0B0ZXN0LmNvbSIsIkNvdW50cnkiOiJBbnRhcnRpY2EifQ.5Xu7v2MosIQmtAOlqfM2PE9eJeT0iZzL9x6RIvqx_PAHKer0ylo-0wT9eON_qX1H_QZekTWMf8ok4fxdZNv2KP_AkNqSKLXYJ65TjPnfcX8-dooDJM9txfRWOFqJWx4yj4CTMPNR6rNhizkC9jUaLisPIjogc_a_61qTSnvHXFnuaYmkovN2Y3WfuXjhUZCH98hodRL_ATg1_SpO0bPb7_N1Z76yrcv0RYQan0Y5kICWYdhHlk8Dw6I2fLMVsl3HiYiRq4XBJE8AY_g742Uq5kTS62PKohg3IjfRa-g2rjgKo1XW2sRLVc7vnns2L3TqESo5vgvorTjKnCTQKuHpIg

Encrypted JWT

加密的 JWT

eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIn0.lZ2nqCeiPzsPmJShsrDD3uA55-06A649CMtwOyuY9nNzMtUGyzV-G8qc4w4ui1uWrtzypBs5Eyq4GfjnTtVHbcDVkS1HVc3tfxNAPY8dfjVrWNz59HyKt4bCjBdqqhBOdZezLtWB9aoWIwZoHLf4D8aUcVUtDsFELVcScmiQNtzHwvpDHZb4oxRfPl-OuOTkKA23C8lnnDMO1KUy8ZXHD4p0jQKAcaV877gYm8NbHDwOBEf-ItWJOGx2jV60apWd0hKqwfFR2QKD9wmGgXpbFZ08ro7X2fj8rTgKWhDgoBT_JVZdVFhVI4T4RLRDrCJqkyeciXhLm7W_xNhWBXAMrA.94SuB596ZLuUtw53wrofwN5jZXfT5f-ZarJhQc9Mj0M.0Ow5DXfilYX3ty49H4lNMNPljlWAFASc49zljhRSIIUSlmUHLZo0SAezn-n_FdxexAIYLk_FtRgnkMHDEyxJ1V1yHhqa1Jvdb36lTYyptqCJhMkOV1XGn58L4Z9QQmdrIZnn5iHxZ9-N1Jfjs0eoKiLBgR9O7ZEcs7QrWZVT6n_HrGrIloYQu_lFgmk5O7k47_15CVXaFqIohpHXETejoHEwjQj-iTToNRaHWNFAKvlpUBz4mUgk9RSIQCxK1GxxS8wxP44w5G4HdOIjFNwTsRDXeSZy0mU9zTNUCmDEUT9MFESfmVU1nPurdT-VoiPvVklbJZW8Sas0hWgqQkdQdP35nFY1sjCgfMB9iYUeEU-TCE219wkm1XXrLJwLEYZclL_4ckl4zExo2wb3Czwd8f5iO9fBQQWZ4mdwThK4VtZaPs1JEkxwGLI0SHA8Jr-e2PsDrkGEnxs74FsJ5MKluU2ZKvKcGXyQPaaTRa0ecJLD5-YYBuTtxOnU3gM_5aZm97pd_wiPk_h81r5aiwjSfRF3Ihxp37KNPfNOMJoA9xe2F51m1AvmjrOUgSM156LwmFyJFebVfarb9NPtJ_q1wU891sCu2Vmv520BR4QfIc-ayIwTVxLgZSN-BP7PhEJb_x8.XhZpINBxRdFFEgwPTcAgJg

Same code runs seperately for signing and encryption, but didn't run, if I include both.

相同的代码单独运行以进行签名和加密,但如果我同时包含两者,则不会运行。

Please help me to understand what I'm doing wrong.

请帮助我了解我做错了什么。

Thanks In Advance

提前致谢

采纳答案by frasertweedale

A JWT has as its payload, or Message, the UTF-8 representation of the Claims Set. From RFC 7519:

JWT将Claims Set的 UTF-8 表示作为其有效负载或Message。来自RFC 7519

Let the Message be the octets of the UTF-8 representation of the JWT Claims Set.

让 Message 是 JWT 声明集的 UTF-8 表示的八位字节。

This is the case for both signed JWTs (which are JWS objects), and encrypted JWTs (using JWE):

签名的 JWT(它们是 JWS 对象)和加密的 JWT(使用 JWE)都是这种情况:

if the JWT is a JWE, create a JWE using the Message as the plaintext for the JWE; all steps specified in JWE for creating a JWE MUST be followed.

如果 JWT 是 JWE,则使用 Message 作为 JWE 的明文创建 JWE;必须遵循 JWE 中指定的用于创建 JWE 的所有步骤。

Accordingly, for verification of an encrypted JWT, the payload is interpreted as a Claims Set:

因此,为了验证加密的 JWT,有效载荷被解释为声明集:

Else, if the JWT is a JWE, follow the steps specified in JWE for validating a JWE. Let the Message be the resulting plaintext.

否则,如果 JWT 是 JWE,请按照 JWE 中指定的步骤验证 JWE。让 Message 成为结果明文。

The mistake you have made in your program is using the serialization of the signed JWT as the payload of a JWE, but then attempting to process the resulting object as n encryptedJWE. Accordingly, the library attempts to interpret a serialized signed JWT (the JWS Flattened Serialization) as a serialized JWT Claims Set (a JSON object). This explains the exception you are getting:

您在程序中犯的错误是使用已签名 JWT 的序列化作为 JWE 的有效负载,然后尝试将生成的对象作为 n加密的JWE 进行处理。因此,库尝试将序列化的签名 JWT(JWS Flattened Serialization)解释为序列化的 JWT 声明集(JSON 对象)。这解释了您遇到的异常:

Caused by: org.jose4j.lang.JoseException: Parsing error:
    org.jose4j.json.internal.json_simple.parser.ParseException:
    Unexpected character (e) at position 0.

It appears that you are attempting to produce a JWT that is both encrypted and authenticated. All JWE algorithms are authenticated encryptionalgorithms, so there is no need to do anything with JWS to achieve this - an encrypted JWT is sufficient.

您似乎正在尝试生成既加密又经过身份验证的 JWT。所有 JWE 算法都是经过身份验证的加密算法,因此不需要对 JWS 做任何事情来实现这一点——一个加密的 JWT 就足够了。

回答by Brian Campbell

For a nested JWT (i.e. JWE[JWS[JSON Claims]] which is what you're dong), the "cty" (content type) header of the the JWE is supposed to have a value of "JWT" to indicate that the payload is itself a JWT. The definition of"cty" in the JWT spec, RFC 7519, talks about that a bit more. It helps the consumer/receiver know how to process things.

对于嵌套的 JWT(即 JWE[JWS[JSON Claims]],这就是你的东东),JWE 的“cty”(内容类型)标头应该具有“JWT”值,以表明负载本身就是一个 JWT。JWT 规范RFC 7519中“cty”的定义对此进行了更多讨论。它帮助消费者/接收者知道如何处理事物。

The exception you're seeing is the result of the library trying to parse the JWS compact serialization, which is the payload of the JWE, as JSON.

您看到的例外是库尝试将 JWS 紧凑序列化(JWE 的有效负载)解析为 JSON 的结果。

According to spec, you really should set the cty header to "JWT" on the JWE, which indicates that the JWE payload is itself a JWT. That can be done with jwe.setHeader(HeaderParameterNames.CONTENT_TYPE, "JWT");or jwe.setContentTypeHeaderValue("JWT")as of v0.4.2.

根据规范,您确实应该在 JWE 上将 cty 标头设置为“JWT”,这表明 JWE 负载本身就是 JWT。这可以通过v0.4.2jwe.setHeader(HeaderParameterNames.CONTENT_TYPE, "JWT");jwe.setContentTypeHeaderValue("JWT")从 v0.4.2 开始完成。

You can also tell the JwtConsumerto be a bit more liberal in processing and make a best effort when the cty header isn't present and the payload doesn't parse as JSON but can be parsed into a JOSE object. This can be done with .setEnableLiberalContentTypeHandling()on the JwtConsumerBuilder.

JwtConsumer当 cty 标头不存在且有效负载未解析为 JSON 但可以解析为 JOSE 对象时,您还可以告诉它在处理时更加自由一些并尽最大努力。这可以.setEnableLiberalContentTypeHandling()JwtConsumerBuilder.

Couple more observations:

更多观察:

You don't need to set the content encryption key or the IV on the JWE. The library uses a secure random to generate those for you with the appropriate lengths. So the following should be sufficent,

您无需在 JWE 上设置内容加密密钥或 IV。该库使用安全随机数为您生成具有适当长度的随机数。所以以下应该就足够了,

JsonWebEncryption jwe = new JsonWebEncryption();
jwe.setHeader(HeaderParameterNames.CONTENT_TYPE, "JWT");
jwe.setKey(keyEncrypt.getPublicKey());
jwe.setPayload(signedJwt);
jwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.RSA_OAEP_256);
jwe.setEncryptionMethodHeaderParameter(ContentEncryptionAlgorithmIdentifiers.AES_256_GCM);
String encryptedJwt = jwe.getCompactSerialization();
System.out.println("Encrypted ::" + encryptedJwt);

Also, I'm guessing from the use of RSA_OAEP_256 and AES_256_GCM that you're using Bouncy Castle. I'd strongly recommend upgrading to jose4j 0.4.4 due to a security vulnerability that was identified when using the library with the Bouncy Castle security provider. See the Release Notes on v 0.4.4 for more info https://bitbucket.org/b_c/jose4j/wiki/Release%20Notes#!jose4j-044-july-24-2015

另外,我从 RSA_OAEP_256 和 AES_256_GCM 的使用中猜测您正在使用 Bouncy Castle。我强烈建议升级到 jose4j 0.4.4,因为在使用 Bouncy Castle 安全提供程序的库时发现了一个安全漏洞。有关更多信息,请参阅 v 0.4.4 的发行说明 https://bitbucket.org/b_c/jose4j/wiki/Release%20Notes#!jose4j-044-july-24-2015