Describe the bug If an IdP sends an ID token with claim amr, the Jackson ObjectMapper with SecurityJackson2Modules cannot serialize the ID token to JSON (related: https://github.com/spring-projects/spring-security/issues/4370). The amr claim in the ID token has the type com.nimbusds.jose.shaded.json.JSONArray for which there is no default mixin.

Tested with Spring-Security 5.4.1.

To Reproduce These steps resemble a normal oauth2Login configuration where additionally the ID token is serialized to JSON. 1. Include an amr claim in the ID token 2. Decode the string token value using an JwtDecoder created by OidcIdTokenDecoderFactory to a Jwt. 3. Create an OidcIdToken from the Jwt. 4. Serialize the OidcIdToken to a JSON string using an ObjectMapper with the SecurityJackson2Modules.

Expected behavior The amr claim should be an ArrayList instead of JSONArray.

Workaround Define a mixin for the JSONArray class.

Comment From: jgrandja

Thanks for the report @mengelbrecht. The default converter for the amr claim is Collection<String> in OidcIdTokenDecoderFactory.createDefaultClaimTypeConverters().

Can you put together a test that reproduces the issue as I'm not seeing how the amr claim is decoded as a com.nimbusds.jose.shaded.json.JSONArray.

Comment From: mengelbrecht

I could not create a test because the JWT has to be signed and the validator wants to fetch the jwks which fails in my test.

After a little more digging I could reproduce it using this Kotlin snippet. The code outputs com.nimbusds.jose.shaded.json.JSONArray as type for the converted object.

    val jsonArray = com.nimbusds.jose.shaded.json.JSONArray().apply { add("test") }
    val converted = ClaimConversionService.getSharedInstance().convert(
        jsonArray,
        TypeDescriptor.valueOf(Any::class.java),
        TypeDescriptor.collection(Collection::class.java, TypeDescriptor.valueOf(String::class.java))
    )
    println(converted.javaClass.name)

When NimbusJwtProcessor processes the parsed JWT the JWTClaimsSet contains the JSONArray for the amr claim: https://github.com/spring-projects/spring-security/blob/2abf59b695b3ad14719299ed17ff47b181eed802/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java#L147

Just as you mentioned the ObjectToListStringConverter then tries to convert it to a Collection<String> here: https://github.com/spring-projects/spring-security/blob/2abf59b695b3ad14719299ed17ff47b181eed802/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java#L149

However, since JSONArray subclasses List<Object> and the first element is of type String the converter decides that there is nothing to do and just returns the source which then still is a JSONArray: https://github.com/spring-projects/spring-security/blob/ef3b4d49d9802f573b0d14ac4b06e4b7a94c1d4a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ObjectToListStringConverter.java#L62

Comment From: jgrandja

Good catch @mengelbrecht !

So the issue is in ObjectToListStringConverter: https://github.com/spring-projects/spring-security/blob/2abf59b695b3ad14719299ed17ff47b181eed802/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ObjectToListStringConverter.java#L62

Instead of returning the source as-is, it should instead convert to a new ArrayList<String>().

Would you be interested in submitting this fix?

Comment From: mengelbrecht

@jgrandja unfortunately I don't have the time at the moment, sorry

Comment From: jgrandja

No worries @mengelbrecht. Thanks for reporting this!

Comment From: ghost

hi @jgrandja . I have some spare time and I can submit a PR for this today or tomorrow.

Comment From: jgrandja

That would be great @ovidiupopa91. Thank you!

Comment From: jgrandja

@ovidiupopa91 Looks like we could run into the same problem with ObjectToMapStringObjectConverter: https://github.com/spring-projects/spring-security/blob/2abf59b695b3ad14719299ed17ff47b181eed802/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ObjectToMapStringObjectConverter.java#L57

It returns the Map as-is, which could be a com.nimbusds.jose.shaded.json.JSONObject. Could you also update this to return a new Map, as part of the PR. Thanks!