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!