From 88ea668f47515ecbbb9406c68c813589f1795a34 Mon Sep 17 00:00:00 2001 From: Ziqin Wang Date: Sun, 15 Mar 2026 12:27:41 +0800 Subject: [PATCH 1/6] Test Jackson 2 deserializer with unknown obj/arr WebAuthn ext Signed-off-by: Ziqin Wang --- .../web/webauthn/jackson/JacksonTests.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/web/src/test/java/org/springframework/security/web/webauthn/jackson/JacksonTests.java b/web/src/test/java/org/springframework/security/web/webauthn/jackson/JacksonTests.java index ba970125e0..d214c4dd2c 100644 --- a/web/src/test/java/org/springframework/security/web/webauthn/jackson/JacksonTests.java +++ b/web/src/test/java/org/springframework/security/web/webauthn/jackson/JacksonTests.java @@ -122,6 +122,30 @@ class JacksonTests { assertThat(outputs).usingRecursiveComparison().isEqualTo(credProps); } + @Test + void readAuthenticationExtensionsClientOutputsWhenUnknownExtension() throws Exception { + String json = """ + { + "unknownObject1": { + "key": "value" + }, + "unknownArray": [ + { "key": "value1" }, + { "key": "value2" } + ], + "credProps": { + "rk": false + }, + "unknownObject2": {} + } + """; + CredentialPropertiesOutput credProps = new CredentialPropertiesOutput(false); + + AuthenticationExtensionsClientOutputs outputs = this.mapper.readValue(json, + AuthenticationExtensionsClientOutputs.class); + assertThat(outputs.getOutputs()).usingRecursiveFieldByFieldElementComparator().contains(credProps); + } + @Test void readAuthenticationExtensionsClientOutputsWhenFieldAfter() throws Exception { String json = """ From a7039fb3e6e5424829788f139944a7eb0c9da3b6 Mon Sep 17 00:00:00 2001 From: Ziqin Wang Date: Sun, 15 Mar 2026 12:30:13 +0800 Subject: [PATCH 2/6] Test Jackson 2 deserializer with unknown primitive WebAuthn ext Signed-off-by: Ziqin Wang --- .../web/webauthn/jackson/JacksonTests.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/web/src/test/java/org/springframework/security/web/webauthn/jackson/JacksonTests.java b/web/src/test/java/org/springframework/security/web/webauthn/jackson/JacksonTests.java index d214c4dd2c..f93b4ecd3f 100644 --- a/web/src/test/java/org/springframework/security/web/webauthn/jackson/JacksonTests.java +++ b/web/src/test/java/org/springframework/security/web/webauthn/jackson/JacksonTests.java @@ -122,6 +122,23 @@ class JacksonTests { assertThat(outputs).usingRecursiveComparison().isEqualTo(credProps); } + @Test + void readAuthenticationExtensionsClientOutputsWhenAppId() throws Exception { + String json = """ + { + "appid": false, + "credProps": { + "rk": false + } + } + """; + CredentialPropertiesOutput credProps = new CredentialPropertiesOutput(false); + + AuthenticationExtensionsClientOutputs outputs = this.mapper.readValue(json, + AuthenticationExtensionsClientOutputs.class); + assertThat(outputs.getOutputs()).usingRecursiveFieldByFieldElementComparator().contains(credProps); + } + @Test void readAuthenticationExtensionsClientOutputsWhenUnknownExtension() throws Exception { String json = """ From e726c05e764faf23961bff7071f43b92ce78597c Mon Sep 17 00:00:00 2001 From: Ziqin Wang Date: Sun, 15 Mar 2026 12:33:28 +0800 Subject: [PATCH 3/6] Fix Jackson 2 deserializer for AuthenticationExtensionsClientOutputs The deserializer is updated to properly ignore unknown extensions. Closes gh-18643 Signed-off-by: Ziqin Wang --- ...enticationExtensionsClientOutputsDeserializer.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientOutputsDeserializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientOutputsDeserializer.java index f1d18e6f23..3a46fe9ddd 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientOutputsDeserializer.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientOutputsDeserializer.java @@ -56,11 +56,8 @@ class AuthenticationExtensionsClientOutputsDeserializer extends StdDeserializer< throws IOException, JacksonException { List> outputs = new ArrayList<>(); for (String key = parser.nextFieldName(); key != null; key = parser.nextFieldName()) { - JsonToken startObject = parser.nextValue(); - if (startObject != JsonToken.START_OBJECT) { - break; - } - if (CredentialPropertiesOutput.EXTENSION_ID.equals(key)) { + JsonToken next = parser.nextToken(); + if (next == JsonToken.START_OBJECT && CredentialPropertiesOutput.EXTENSION_ID.equals(key)) { CredentialPropertiesOutput output = parser.readValueAs(CredentialPropertiesOutput.class); outputs.add(output); } @@ -68,7 +65,9 @@ class AuthenticationExtensionsClientOutputsDeserializer extends StdDeserializer< if (logger.isDebugEnabled()) { logger.debug("Skipping unknown extension with id " + key); } - parser.nextValue(); + if (next.isStructStart()) { + parser.skipChildren(); + } } } From 7f75fd611e69c238074a716dd259a25de4563c9c Mon Sep 17 00:00:00 2001 From: Ziqin Wang Date: Sun, 15 Mar 2026 12:28:33 +0800 Subject: [PATCH 4/6] Test Jackson 3 deserializer with unknown obj/arr WebAuthn ext Signed-off-by: Ziqin Wang --- .../web/webauthn/jackson/JacksonTests.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/webauthn/src/test/java/org/springframework/security/web/webauthn/jackson/JacksonTests.java b/webauthn/src/test/java/org/springframework/security/web/webauthn/jackson/JacksonTests.java index 6c68662e80..1b511da78d 100644 --- a/webauthn/src/test/java/org/springframework/security/web/webauthn/jackson/JacksonTests.java +++ b/webauthn/src/test/java/org/springframework/security/web/webauthn/jackson/JacksonTests.java @@ -121,6 +121,30 @@ class JacksonTests { assertThat(outputs).usingRecursiveComparison().isEqualTo(credProps); } + @Test + void readAuthenticationExtensionsClientOutputsWhenUnknownExtension() { + String json = """ + { + "unknownObject1": { + "key": "value" + }, + "unknownArray": [ + { "key": "value1" }, + { "key": "value2" } + ], + "credProps": { + "rk": false + }, + "unknownObject2": {} + } + """; + CredentialPropertiesOutput credProps = new CredentialPropertiesOutput(false); + + AuthenticationExtensionsClientOutputs outputs = this.mapper.readValue(json, + AuthenticationExtensionsClientOutputs.class); + assertThat(outputs.getOutputs()).usingRecursiveFieldByFieldElementComparator().contains(credProps); + } + @Test void readAuthenticationExtensionsClientOutputsWhenFieldAfter() throws Exception { String json = """ From 65bf54d842ffedf082bf014b1260f61d64a8e1c2 Mon Sep 17 00:00:00 2001 From: Ziqin Wang Date: Sun, 15 Mar 2026 12:30:57 +0800 Subject: [PATCH 5/6] Test Jackson 3 deserializer with unknown primitive WebAuthn ext Signed-off-by: Ziqin Wang --- .../web/webauthn/jackson/JacksonTests.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/webauthn/src/test/java/org/springframework/security/web/webauthn/jackson/JacksonTests.java b/webauthn/src/test/java/org/springframework/security/web/webauthn/jackson/JacksonTests.java index 1b511da78d..bab0a1ca1b 100644 --- a/webauthn/src/test/java/org/springframework/security/web/webauthn/jackson/JacksonTests.java +++ b/webauthn/src/test/java/org/springframework/security/web/webauthn/jackson/JacksonTests.java @@ -121,6 +121,23 @@ class JacksonTests { assertThat(outputs).usingRecursiveComparison().isEqualTo(credProps); } + @Test + void readAuthenticationExtensionsClientOutputsWhenAppId() { + String json = """ + { + "appid": false, + "credProps": { + "rk": false + } + } + """; + CredentialPropertiesOutput credProps = new CredentialPropertiesOutput(false); + + AuthenticationExtensionsClientOutputs outputs = this.mapper.readValue(json, + AuthenticationExtensionsClientOutputs.class); + assertThat(outputs.getOutputs()).usingRecursiveFieldByFieldElementComparator().contains(credProps); + } + @Test void readAuthenticationExtensionsClientOutputsWhenUnknownExtension() { String json = """ From ae827b6e1b6776a42a79016af9f662ce7a75546c Mon Sep 17 00:00:00 2001 From: Ziqin Wang Date: Sun, 15 Mar 2026 12:35:19 +0800 Subject: [PATCH 6/6] Fix Jackson 3 deserializer for AuthenticationExtensionsClientOutputs The deserializer is updated to properly ignore unknown extensions. This fix addresses the WebAuthn authentication failure appeared when using FIDO2 security keys on Safari. Closes gh-18643 Signed-off-by: Ziqin Wang --- ...enticationExtensionsClientOutputsDeserializer.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientOutputsDeserializer.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientOutputsDeserializer.java index afc2d8511b..d1586b34b8 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientOutputsDeserializer.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientOutputsDeserializer.java @@ -55,11 +55,8 @@ class AuthenticationExtensionsClientOutputsDeserializer extends StdDeserializer< throws JacksonException { List> outputs = new ArrayList<>(); for (String key = parser.nextName(); key != null; key = parser.nextName()) { - JsonToken startObject = parser.nextValue(); - if (startObject != JsonToken.START_OBJECT) { - break; - } - if (CredentialPropertiesOutput.EXTENSION_ID.equals(key)) { + JsonToken next = parser.nextToken(); + if (next == JsonToken.START_OBJECT && CredentialPropertiesOutput.EXTENSION_ID.equals(key)) { CredentialPropertiesOutput output = parser.readValueAs(CredentialPropertiesOutput.class); outputs.add(output); } @@ -67,7 +64,9 @@ class AuthenticationExtensionsClientOutputsDeserializer extends StdDeserializer< if (logger.isDebugEnabled()) { logger.debug("Skipping unknown extension with id " + key); } - parser.nextValue(); + if (next.isStructStart()) { + parser.skipChildren(); + } } }