Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 802191d

Browse files
committedJan 20, 2025·
Add serialize/deserialize PublicKeyCredentialCreationOptions and PublicKeyCredentialRequestOptions support with ObjectMapper
gh-16433 PublicKeyCredentialRequestOptions gh-16434 PublicKeyCredentialCreationOptions
1 parent d3332e1 commit 802191d

File tree

37 files changed

+1241
-44
lines changed

37 files changed

+1241
-44
lines changed
 

‎web/spring-security-web.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ dependencies {
3636
api 'org.springframework:spring-web'
3737

3838
optional 'com.fasterxml.jackson.core:jackson-databind'
39+
optional 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
3940
optional 'io.projectreactor:reactor-core'
4041
optional 'org.springframework:spring-jdbc'
4142
optional 'org.springframework:spring-tx'

‎web/src/main/java/org/springframework/security/web/webauthn/api/AuthenticatorTransport.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
* order to obtain an assertion for a specific credential.
2424
*
2525
* @author Rob Winch
26+
* @author Justin Cranford
2627
* @since 6.4
2728
*/
2829
public final class AuthenticatorTransport {
@@ -112,7 +113,7 @@ public static AuthenticatorTransport valueOf(String value) {
112113
}
113114

114115
public static AuthenticatorTransport[] values() {
115-
return new AuthenticatorTransport[] { USB, NFC, BLE, HYBRID, INTERNAL };
116+
return new AuthenticatorTransport[] { USB, NFC, BLE, SMART_CARD, HYBRID, INTERNAL };
116117
}
117118

118119
}

‎web/src/main/java/org/springframework/security/web/webauthn/api/COSEAlgorithmIdentifier.java

+21
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
* used to identify a cryptographic algorithm.
2323
*
2424
* @author Rob Winch
25+
* @author Justin Cranford
2526
* @since 6.4
2627
* @see PublicKeyCredentialParameters#getAlg()
2728
*/
@@ -62,4 +63,24 @@ public static COSEAlgorithmIdentifier[] values() {
6263
return new COSEAlgorithmIdentifier[] { EdDSA, ES256, ES384, ES512, RS256, RS384, RS512, RS1 };
6364
}
6465

66+
public static COSEAlgorithmIdentifier valueOf(long value) {
67+
if (COSEAlgorithmIdentifier.EdDSA.getValue() == value) {
68+
return EdDSA;
69+
} else if (COSEAlgorithmIdentifier.ES256.getValue() == value) {
70+
return ES256;
71+
} else if (COSEAlgorithmIdentifier.ES384.getValue() == value) {
72+
return ES384;
73+
} else if (COSEAlgorithmIdentifier.ES512.getValue() == value) {
74+
return ES512;
75+
} else if (COSEAlgorithmIdentifier.RS256.getValue() == value) {
76+
return RS256;
77+
} else if (COSEAlgorithmIdentifier.RS384.getValue() == value) {
78+
return RS384;
79+
} else if (COSEAlgorithmIdentifier.RS512.getValue() == value) {
80+
return RS512;
81+
} else if (COSEAlgorithmIdentifier.RS1.getValue() == value) {
82+
return RS1;
83+
}
84+
return new COSEAlgorithmIdentifier(value);
85+
}
6586
}

‎web/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialParameters.java

+22
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
* is used to supply additional parameters when creating a new credential.
2323
*
2424
* @author Rob Winch
25+
* @author Justin Cranford
2526
* @since 6.4
2627
* @see PublicKeyCredentialCreationOptions#getPubKeyCredParams()
2728
*/
@@ -96,4 +97,25 @@ public COSEAlgorithmIdentifier getAlg() {
9697
return this.alg;
9798
}
9899

100+
public static PublicKeyCredentialParameters valueOf(PublicKeyCredentialType type, COSEAlgorithmIdentifier alg) {
101+
if (PublicKeyCredentialParameters.EdDSA.getType().equals(type) && PublicKeyCredentialParameters.EdDSA.getAlg().equals(alg)) {
102+
return EdDSA;
103+
} else if (PublicKeyCredentialParameters.ES256.getType().equals(type) && PublicKeyCredentialParameters.ES256.getAlg().equals(alg)) {
104+
return ES256;
105+
} else if (PublicKeyCredentialParameters.ES384.getType().equals(type) && PublicKeyCredentialParameters.ES256.getAlg().equals(alg)) {
106+
return ES384;
107+
} else if (PublicKeyCredentialParameters.ES512.getType().equals(type) && PublicKeyCredentialParameters.ES256.getAlg().equals(alg)) {
108+
return ES512;
109+
} else if (PublicKeyCredentialParameters.RS256.getType().equals(type) && PublicKeyCredentialParameters.ES256.getAlg().equals(alg)) {
110+
return RS256;
111+
} else if (PublicKeyCredentialParameters.RS384.getType().equals(type) && PublicKeyCredentialParameters.ES256.getAlg().equals(alg)) {
112+
return RS384;
113+
} else if (PublicKeyCredentialParameters.RS512.getType().equals(type) && PublicKeyCredentialParameters.ES256.getAlg().equals(alg)) {
114+
return RS512;
115+
} else if (PublicKeyCredentialParameters.RS1.getType().equals(type) && PublicKeyCredentialParameters.ES256.getAlg().equals(alg)) {
116+
return RS1;
117+
}
118+
return new PublicKeyCredentialParameters(type, alg);
119+
}
120+
99121
}

‎web/src/main/java/org/springframework/security/web/webauthn/api/UserVerificationRequirement.java

+21
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
* is used by the Relying Party to indicate if user verification is needed.
2323
*
2424
* @author Rob Winch
25+
* @author Justin Cranford
2526
* @since 6.4
2627
*/
2728
public final class UserVerificationRequirement {
@@ -66,4 +67,24 @@ public String getValue() {
6667
return this.value;
6768
}
6869

70+
/**
71+
* Gets the value
72+
* @param value the string
73+
* @return the value
74+
* @author Justin Cranford
75+
* @since 6.5
76+
*/
77+
public static UserVerificationRequirement valueOf(String value) {
78+
if (DISCOURAGED.getValue().equals(value)) {
79+
return DISCOURAGED;
80+
}
81+
if (PREFERRED.getValue().equals(value)) {
82+
return PREFERRED;
83+
}
84+
if (REQUIRED.getValue().equals(value)) {
85+
return REQUIRED;
86+
}
87+
return new UserVerificationRequirement(value);
88+
}
89+
6990
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.webauthn.jackson;
18+
19+
import com.fasterxml.jackson.core.JsonParser;
20+
import com.fasterxml.jackson.core.JsonToken;
21+
import com.fasterxml.jackson.databind.DeserializationContext;
22+
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
23+
import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInput;
24+
import org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInput;
25+
26+
import java.io.IOException;
27+
28+
/**
29+
* Jackson deserializer for {@link AuthenticationExtensionsClientInput}
30+
*
31+
* @author Justin Cranford
32+
* @since 6.5
33+
*/
34+
@SuppressWarnings("serial")
35+
class AuthenticationExtensionsClientInputDeserializer extends StdDeserializer<AuthenticationExtensionsClientInput> {
36+
37+
AuthenticationExtensionsClientInputDeserializer() {
38+
super(AuthenticationExtensionsClientInput.class);
39+
}
40+
41+
@Override
42+
public AuthenticationExtensionsClientInput deserialize(JsonParser parser, DeserializationContext ctxt)
43+
throws IOException {
44+
if (parser.nextToken() != JsonToken.END_OBJECT) {
45+
String extensionId = parser.currentName();
46+
Object value = parser.readValueAs(Object.class);
47+
return new ImmutableAuthenticationExtensionsClientInput(extensionId, value);
48+
}
49+
return null;
50+
}
51+
}

‎web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputMixin.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,19 @@
1616

1717
package org.springframework.security.web.webauthn.jackson;
1818

19+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
1920
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
20-
2121
import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInputs;
2222

2323
/**
2424
* Jackson mixin for {@link AuthenticationExtensionsClientInputs}
2525
*
2626
* @author Rob Winch
27+
* @author Justin Cranford
2728
* @since 6.4
2829
*/
2930
@JsonSerialize(using = AuthenticationExtensionsClientInputSerializer.class)
31+
@JsonDeserialize(using = AuthenticationExtensionsClientInputDeserializer.class)
3032
class AuthenticationExtensionsClientInputMixin {
3133

3234
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.webauthn.jackson;
18+
19+
import com.fasterxml.jackson.core.JsonParser;
20+
import com.fasterxml.jackson.core.JsonToken;
21+
import com.fasterxml.jackson.databind.DeserializationContext;
22+
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
23+
import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInput;
24+
import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInputs;
25+
import org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInputs;
26+
27+
import java.io.IOException;
28+
import java.util.ArrayList;
29+
import java.util.List;
30+
31+
/**
32+
* Jackson deserializer for {@link AuthenticationExtensionsClientInputs}
33+
*
34+
* @author Justin Cranford
35+
* @since 6.5
36+
*/
37+
@SuppressWarnings("serial")
38+
class AuthenticationExtensionsClientInputsDeserializer extends StdDeserializer<AuthenticationExtensionsClientInputs> {
39+
40+
AuthenticationExtensionsClientInputsDeserializer() {
41+
super(AuthenticationExtensionsClientInputs.class);
42+
}
43+
44+
@Override
45+
public AuthenticationExtensionsClientInputs deserialize(JsonParser parser, DeserializationContext ctxt)
46+
throws IOException {
47+
final AuthenticationExtensionsClientInputDeserializer authenticationExtensionsClientInputDeserializer = new AuthenticationExtensionsClientInputDeserializer();
48+
49+
final List<AuthenticationExtensionsClientInput> extensions = new ArrayList<>();
50+
while (parser.nextToken() != JsonToken.END_OBJECT) {
51+
extensions.add(authenticationExtensionsClientInputDeserializer.deserialize(parser, ctxt));
52+
}
53+
return new ImmutableAuthenticationExtensionsClientInputs(extensions);
54+
}
55+
}

‎web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputsMixin.java

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.security.web.webauthn.jackson;
1818

19+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
1920
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
2021

2122
import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInputs;
@@ -27,6 +28,7 @@
2728
* @since 6.4
2829
*/
2930
@JsonSerialize(using = AuthenticationExtensionsClientInputsSerializer.class)
31+
@JsonDeserialize(using = AuthenticationExtensionsClientInputsDeserializer.class)
3032
class AuthenticationExtensionsClientInputsMixin {
3133

3234
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.webauthn.jackson;
18+
19+
import com.fasterxml.jackson.core.JsonParser;
20+
import com.fasterxml.jackson.core.JsonToken;
21+
import com.fasterxml.jackson.databind.DeserializationContext;
22+
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
23+
import org.springframework.security.web.webauthn.api.AuthenticatorAttachment;
24+
import org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria;
25+
import org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria.AuthenticatorSelectionCriteriaBuilder;
26+
import org.springframework.security.web.webauthn.api.ResidentKeyRequirement;
27+
import org.springframework.security.web.webauthn.api.UserVerificationRequirement;
28+
29+
import java.io.IOException;
30+
31+
/**
32+
* Jackson deserializer for {@link AuthenticatorSelectionCriteria}
33+
*
34+
* @author Justin Cranford
35+
* @since 6.5
36+
*/
37+
@SuppressWarnings("serial")
38+
class AuthenticatorSelectionCriteriaDeserializer extends StdDeserializer<AuthenticatorSelectionCriteria> {
39+
40+
AuthenticatorSelectionCriteriaDeserializer() {
41+
super(AuthenticatorSelectionCriteria.class);
42+
}
43+
44+
@Override
45+
public AuthenticatorSelectionCriteria deserialize(JsonParser parser, DeserializationContext ctxt)
46+
throws IOException {
47+
final AuthenticatorSelectionCriteriaBuilder builder = AuthenticatorSelectionCriteria.builder();
48+
while (parser.nextToken() != JsonToken.END_OBJECT) {
49+
final String fieldName = parser.currentName();
50+
parser.nextToken();
51+
if ("authenticatorAttachment".equals(fieldName)) {
52+
builder.authenticatorAttachment(AuthenticatorAttachment.valueOf(parser.getText()));
53+
} else if ("residentKey".equals(fieldName)) {
54+
builder.residentKey(ResidentKeyRequirement.valueOf(parser.getText()));
55+
} else if ("userVerification".equals(fieldName)) {
56+
builder.userVerification(UserVerificationRequirement.valueOf(parser.getText()));
57+
} else {
58+
throw new IOException("Unsupported field name: " + fieldName);
59+
}
60+
}
61+
return builder.build();
62+
}
63+
64+
}

‎web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorSelectionCriteriaMixin.java

+5
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,19 @@
1818

1919
import com.fasterxml.jackson.annotation.JsonInclude;
2020

21+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
22+
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
2123
import org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria;
2224

2325
/**
2426
* Jackson mixin for {@link AuthenticatorSelectionCriteria}
2527
*
2628
* @author Rob Winch
29+
* @author Justin Cranford
2730
* @since 6.4
2831
*/
32+
@JsonSerialize(using = AuthenticatorSelectionCriteriaSerializer.class)
33+
@JsonDeserialize(using = AuthenticatorSelectionCriteriaDeserializer.class)
2934
@JsonInclude(JsonInclude.Include.NON_NULL)
3035
abstract class AuthenticatorSelectionCriteriaMixin {
3136

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.webauthn.jackson;
18+
19+
import com.fasterxml.jackson.core.JsonGenerator;
20+
import com.fasterxml.jackson.databind.SerializerProvider;
21+
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
22+
import org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria;
23+
24+
import java.io.IOException;
25+
26+
/**
27+
* Jackson serializer for {@link AuthenticatorSelectionCriteria}
28+
*
29+
* @author Justin Cranford
30+
* @since 6.5
31+
*/
32+
@SuppressWarnings("serial")
33+
class AuthenticatorSelectionCriteriaSerializer extends StdSerializer<AuthenticatorSelectionCriteria> {
34+
35+
AuthenticatorSelectionCriteriaSerializer() {
36+
super(AuthenticatorSelectionCriteria.class);
37+
}
38+
39+
@Override
40+
public void serialize(AuthenticatorSelectionCriteria value, JsonGenerator gen, SerializerProvider provider)
41+
throws IOException {
42+
gen.writeStartObject();
43+
if (value.getAuthenticatorAttachment() != null) {
44+
gen.writeFieldName("authenticatorAttachment");
45+
gen.writeString(value.getAuthenticatorAttachment().getValue());
46+
}
47+
if (value.getResidentKey() != null) {
48+
gen.writeFieldName("residentKey");
49+
gen.writeString(value.getResidentKey().getValue());
50+
}
51+
if (value.getUserVerification() != null) {
52+
gen.writeFieldName("userVerification");
53+
gen.writeString(value.getUserVerification().getValue());
54+
}
55+
gen.writeEndObject();
56+
}
57+
58+
}

‎web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorTransportDeserializer.java

+5-11
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,18 @@
1616

1717
package org.springframework.security.web.webauthn.jackson;
1818

19-
import java.io.IOException;
20-
21-
import com.fasterxml.jackson.core.JacksonException;
2219
import com.fasterxml.jackson.core.JsonParser;
2320
import com.fasterxml.jackson.databind.DeserializationContext;
2421
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
25-
2622
import org.springframework.security.web.webauthn.api.AuthenticatorTransport;
2723

24+
import java.io.IOException;
25+
2826
/**
2927
* Jackson deserializer for {@link AuthenticatorTransport}
3028
*
3129
* @author Rob Winch
30+
* @author Justin Cranford
3231
* @since 6.4
3332
*/
3433
@SuppressWarnings("serial")
@@ -40,14 +39,9 @@ class AuthenticatorTransportDeserializer extends StdDeserializer<AuthenticatorTr
4039

4140
@Override
4241
public AuthenticatorTransport deserialize(JsonParser parser, DeserializationContext ctxt)
43-
throws IOException, JacksonException {
42+
throws IOException {
4443
String transportValue = parser.readValueAs(String.class);
45-
for (AuthenticatorTransport transport : AuthenticatorTransport.values()) {
46-
if (transport.getValue().equals(transportValue)) {
47-
return transport;
48-
}
49-
}
50-
return null;
44+
return AuthenticatorTransport.valueOf(transportValue);
5145
}
5246

5347
}

‎web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorTransportSerializer.java

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
* @author Rob Winch
3131
* @since 6.4
3232
*/
33+
@SuppressWarnings("serial")
3334
class AuthenticatorTransportSerializer extends JsonSerializer<AuthenticatorTransport> {
3435

3536
@Override

‎web/src/main/java/org/springframework/security/web/webauthn/jackson/COSEAlgorithmIdentifierDeserializer.java

+5-11
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,18 @@
1616

1717
package org.springframework.security.web.webauthn.jackson;
1818

19-
import java.io.IOException;
20-
21-
import com.fasterxml.jackson.core.JacksonException;
2219
import com.fasterxml.jackson.core.JsonParser;
2320
import com.fasterxml.jackson.databind.DeserializationContext;
2421
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
25-
2622
import org.springframework.security.web.webauthn.api.COSEAlgorithmIdentifier;
2723

24+
import java.io.IOException;
25+
2826
/**
2927
* Jackson serializer for {@link COSEAlgorithmIdentifier}
3028
*
3129
* @author Rob Winch
30+
* @author Justin Cranford
3231
* @since 6.4
3332
*/
3433
@SuppressWarnings("serial")
@@ -40,14 +39,9 @@ class COSEAlgorithmIdentifierDeserializer extends StdDeserializer<COSEAlgorithmI
4039

4140
@Override
4241
public COSEAlgorithmIdentifier deserialize(JsonParser parser, DeserializationContext ctxt)
43-
throws IOException, JacksonException {
42+
throws IOException {
4443
Long transportValue = parser.readValueAs(Long.class);
45-
for (COSEAlgorithmIdentifier identifier : COSEAlgorithmIdentifier.values()) {
46-
if (identifier.getValue() == transportValue.longValue()) {
47-
return identifier;
48-
}
49-
}
50-
return null;
44+
return COSEAlgorithmIdentifier.valueOf(transportValue);
5145
}
5246

5347
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.webauthn.jackson;
18+
19+
import java.io.IOException;
20+
import java.time.Duration;
21+
22+
import com.fasterxml.jackson.core.JsonParser;
23+
import com.fasterxml.jackson.databind.DeserializationContext;
24+
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
25+
26+
/**
27+
* Jackson deserializer for {@link Duration}
28+
*
29+
* @author Justin Cranford
30+
* @since 6.5
31+
*/
32+
@SuppressWarnings("serial")
33+
class DurationDeserializer extends StdDeserializer<Duration> {
34+
35+
/**
36+
* Creates an instance.
37+
*/
38+
DurationDeserializer() {
39+
super(Duration.class);
40+
}
41+
42+
@Override
43+
public Duration deserialize(JsonParser parser, DeserializationContext ctxt)
44+
throws IOException {
45+
long millis = parser.getLongValue();
46+
return Duration.ofMillis(millis);
47+
}
48+
}

‎web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialCreationOptionsMixin.java

+28-9
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,42 @@
1616

1717
package org.springframework.security.web.webauthn.jackson;
1818

19-
import java.time.Duration;
20-
21-
import com.fasterxml.jackson.annotation.JsonInclude;
19+
import com.fasterxml.jackson.annotation.JsonCreator;
20+
import com.fasterxml.jackson.annotation.JsonProperty;
21+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
2222
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
23-
23+
import org.springframework.security.web.webauthn.api.AttestationConveyancePreference;
24+
import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInputs;
25+
import org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria;
26+
import org.springframework.security.web.webauthn.api.Bytes;
2427
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
28+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialDescriptor;
29+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialParameters;
30+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRpEntity;
31+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
32+
33+
import java.time.Duration;
34+
import java.util.List;
2535

2636
/**
2737
* Jackson mixin for {@link PublicKeyCredentialCreationOptions}
2838
*
2939
* @author Rob Winch
40+
* @author Justin Cranford
3041
* @since 6.4
3142
*/
32-
@JsonInclude(JsonInclude.Include.NON_NULL)
3343
abstract class PublicKeyCredentialCreationOptionsMixin {
34-
35-
@JsonSerialize(using = DurationSerializer.class)
36-
private Duration timeout;
37-
44+
@JsonCreator
45+
public PublicKeyCredentialCreationOptionsMixin(
46+
@JsonProperty("rp") PublicKeyCredentialRpEntity rp,
47+
@JsonProperty("user") PublicKeyCredentialUserEntity user,
48+
@JsonProperty("challenge") Bytes challenge,
49+
@JsonProperty("pubKeyCredParams") List<PublicKeyCredentialParameters> pubKeyCredParams,
50+
@JsonProperty("timeout") @JsonSerialize(using=DurationSerializer.class) @JsonDeserialize(using=DurationDeserializer.class) Duration timeout,
51+
@JsonProperty("excludeCredentials") List<PublicKeyCredentialDescriptor> excludeCredentials,
52+
@JsonProperty("authenticatorSelection") AuthenticatorSelectionCriteria authenticatorSelection,
53+
@JsonProperty("attestation") AttestationConveyancePreference attestation,
54+
@JsonProperty("extensions") AuthenticationExtensionsClientInputs extensions
55+
) {
56+
}
3857
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.webauthn.jackson;
18+
19+
import com.fasterxml.jackson.annotation.JsonCreator;
20+
import com.fasterxml.jackson.annotation.JsonProperty;
21+
import org.springframework.security.web.webauthn.api.AuthenticatorTransport;
22+
import org.springframework.security.web.webauthn.api.Bytes;
23+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialDescriptor;
24+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialType;
25+
26+
import java.util.Set;
27+
28+
/**
29+
* Jackson mixin for {@link PublicKeyCredentialDescriptor}
30+
*
31+
* @author Justin Cranford
32+
* @since 6.5
33+
*/
34+
abstract class PublicKeyCredentialDescriptorMixin {
35+
@JsonCreator
36+
public PublicKeyCredentialDescriptorMixin(
37+
@JsonProperty("type") PublicKeyCredentialType type,
38+
@JsonProperty("id") Bytes id,
39+
@JsonProperty("transports") Set<AuthenticatorTransport> transports
40+
) {
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.webauthn.jackson;
18+
19+
import com.fasterxml.jackson.core.JsonParser;
20+
import com.fasterxml.jackson.core.JsonToken;
21+
import com.fasterxml.jackson.databind.DeserializationContext;
22+
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
23+
import org.springframework.security.web.webauthn.api.COSEAlgorithmIdentifier;
24+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialParameters;
25+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialType;
26+
27+
import java.io.IOException;
28+
29+
/**
30+
* Jackson deserializer for {@link PublicKeyCredentialParameters}
31+
*
32+
* @author Justin Cranford
33+
* @since 6.5
34+
*/
35+
@SuppressWarnings("serial")
36+
public class PublicKeyCredentialParametersDeserializer extends StdDeserializer<PublicKeyCredentialParameters> {
37+
38+
public PublicKeyCredentialParametersDeserializer() {
39+
super(PublicKeyCredentialParameters.class);
40+
}
41+
42+
@Override
43+
public PublicKeyCredentialParameters deserialize(JsonParser parser, DeserializationContext ctxt)
44+
throws IOException {
45+
PublicKeyCredentialType type = null;
46+
COSEAlgorithmIdentifier alg = null;
47+
while (parser.nextToken() != JsonToken.END_OBJECT) {
48+
final String fieldName = parser.currentName();
49+
parser.nextToken();
50+
if ("type".equals(fieldName)) {
51+
type = PublicKeyCredentialType.valueOf(parser.getText());
52+
} else if ("alg".equals(fieldName)) {
53+
alg = COSEAlgorithmIdentifier.valueOf(parser.getLongValue());
54+
} else {
55+
throw new IOException("Unsupported field name: " + fieldName);
56+
}
57+
}
58+
return PublicKeyCredentialParameters.valueOf(type, alg);
59+
}
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.webauthn.jackson;
18+
19+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
20+
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
21+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialParameters;
22+
23+
/**
24+
* Jackson mixin for {@link PublicKeyCredentialParameters}
25+
*
26+
* @author Justin Cranford
27+
* @since 6.5
28+
*/
29+
@JsonSerialize(using = PublicKeyCredentialParametersSerializer.class)
30+
@JsonDeserialize(using = PublicKeyCredentialParametersDeserializer.class)
31+
abstract class PublicKeyCredentialParametersMixin {
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.webauthn.jackson;
18+
19+
import com.fasterxml.jackson.core.JsonGenerator;
20+
import com.fasterxml.jackson.databind.SerializerProvider;
21+
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
22+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialParameters;
23+
24+
import java.io.IOException;
25+
26+
/**
27+
* Jackson serializer for {@link PublicKeyCredentialParameters}
28+
*
29+
* @author Justin Cranford
30+
* @since 6.5
31+
*/
32+
@SuppressWarnings("serial")
33+
public class PublicKeyCredentialParametersSerializer extends StdSerializer<PublicKeyCredentialParameters> {
34+
35+
public PublicKeyCredentialParametersSerializer() {
36+
super(PublicKeyCredentialParameters.class);
37+
}
38+
39+
@Override
40+
public void serialize(PublicKeyCredentialParameters value, JsonGenerator gen, SerializerProvider provider) throws IOException {
41+
gen.writeStartObject();
42+
if (value.getType() != null) {
43+
gen.writeFieldName("type");
44+
gen.writeString(value.getType().getValue());
45+
}
46+
if (value.getAlg() != null) {
47+
gen.writeFieldName("alg");
48+
gen.writeNumber(value.getAlg().getValue());
49+
}
50+
gen.writeEndObject();
51+
}
52+
}

‎web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRequestOptionsMixin.java

+21-9
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,35 @@
1616

1717
package org.springframework.security.web.webauthn.jackson;
1818

19-
import java.time.Duration;
20-
21-
import com.fasterxml.jackson.annotation.JsonInclude;
19+
import com.fasterxml.jackson.annotation.JsonCreator;
20+
import com.fasterxml.jackson.annotation.JsonProperty;
21+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
2222
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
23-
23+
import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInputs;
24+
import org.springframework.security.web.webauthn.api.Bytes;
25+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialDescriptor;
2426
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions;
27+
import org.springframework.security.web.webauthn.api.UserVerificationRequirement;
28+
29+
import java.time.Duration;
30+
import java.util.List;
2531

2632
/**
2733
* Jackson mixin for {@link PublicKeyCredentialRequestOptions}
2834
*
2935
* @author Rob Winch
36+
* @author Justin Cranford
3037
* @since 6.4
3138
*/
32-
@JsonInclude(content = JsonInclude.Include.NON_NULL)
3339
class PublicKeyCredentialRequestOptionsMixin {
34-
35-
@JsonSerialize(using = DurationSerializer.class)
36-
private final Duration timeout = null;
37-
40+
@JsonCreator
41+
public PublicKeyCredentialRequestOptionsMixin(
42+
@JsonProperty("challenge") Bytes challenge,
43+
@JsonProperty("timeout") @JsonSerialize(using=DurationSerializer.class) @JsonDeserialize(using=DurationDeserializer.class) Duration timeout,
44+
@JsonProperty("rpId") String rpId,
45+
@JsonProperty("allowCredentials") List<PublicKeyCredentialDescriptor> allowCredentials,
46+
@JsonProperty("userVerification") UserVerificationRequirement userVerification,
47+
@JsonProperty("extensions") AuthenticationExtensionsClientInputs extensions
48+
) {
49+
}
3850
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.webauthn.jackson;
18+
19+
import com.fasterxml.jackson.annotation.JsonCreator;
20+
import com.fasterxml.jackson.annotation.JsonProperty;
21+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRpEntity;
22+
23+
/**
24+
* Jackson mixin for {@link PublicKeyCredentialRpEntity}
25+
*
26+
* @author Justin Cranford
27+
* @since 6.5
28+
*/
29+
abstract class PublicKeyCredentialRpEntityMixin {
30+
@JsonCreator
31+
public PublicKeyCredentialRpEntityMixin(
32+
@JsonProperty("name") String name,
33+
@JsonProperty("id") String id
34+
) {
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.webauthn.jackson;
18+
19+
import com.fasterxml.jackson.core.JsonParser;
20+
import com.fasterxml.jackson.core.JsonToken;
21+
import com.fasterxml.jackson.databind.DeserializationContext;
22+
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
23+
import org.springframework.security.web.webauthn.api.Bytes;
24+
import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity;
25+
import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity.PublicKeyCredentialUserEntityBuilder;
26+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
27+
28+
import java.io.IOException;
29+
30+
/**
31+
* Jackson deserializer for {@link PublicKeyCredentialUserEntity}
32+
*
33+
* @author Justin Cranford
34+
* @since 6.5
35+
*/
36+
@SuppressWarnings("serial")
37+
public class PublicKeyCredentialUserEntityDeserializer extends StdDeserializer<PublicKeyCredentialUserEntity> {
38+
39+
public PublicKeyCredentialUserEntityDeserializer() {
40+
super(PublicKeyCredentialUserEntity.class);
41+
}
42+
43+
@Override
44+
public PublicKeyCredentialUserEntity deserialize(JsonParser parser, DeserializationContext ctxt)
45+
throws IOException {
46+
final PublicKeyCredentialUserEntityBuilder builder = ImmutablePublicKeyCredentialUserEntity.builder();
47+
48+
while (parser.nextToken() != JsonToken.END_OBJECT) {
49+
final String fieldName = parser.currentName();
50+
parser.nextToken();
51+
if ("id".equals(fieldName)) {
52+
builder.id(Bytes.fromBase64(parser.getText()));
53+
} else if ("name".equals(fieldName)) {
54+
builder.name(parser.getText());
55+
} else if ("displayName".equals(fieldName)) {
56+
builder.displayName(parser.getText());
57+
} else {
58+
throw new IOException("Unsupported field name: " + fieldName);
59+
}
60+
}
61+
62+
return builder.build();
63+
}
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.webauthn.jackson;
18+
19+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
20+
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
21+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
22+
23+
/**
24+
* Jackson mixin for {@link PublicKeyCredentialUserEntity}
25+
*
26+
* @author Justin Cranford
27+
* @since 6.5
28+
*/
29+
@JsonSerialize(using = PublicKeyCredentialUserEntitySerializer.class)
30+
@JsonDeserialize(using = PublicKeyCredentialUserEntityDeserializer.class)
31+
abstract class PublicKeyCredentialUserEntityMixin {
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.webauthn.jackson;
18+
19+
import com.fasterxml.jackson.core.JsonGenerator;
20+
import com.fasterxml.jackson.databind.SerializerProvider;
21+
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
22+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
23+
24+
import java.io.IOException;
25+
26+
/**
27+
* Jackson serializer for {@link PublicKeyCredentialUserEntity}
28+
*
29+
* @author Justin Cranford
30+
* @since 6.5
31+
*/
32+
@SuppressWarnings("serial")
33+
public class PublicKeyCredentialUserEntitySerializer extends StdSerializer<PublicKeyCredentialUserEntity> {
34+
35+
public PublicKeyCredentialUserEntitySerializer() {
36+
super(PublicKeyCredentialUserEntity.class);
37+
}
38+
39+
@Override
40+
public void serialize(PublicKeyCredentialUserEntity value, JsonGenerator gen, SerializerProvider provider) throws IOException {
41+
gen.writeStartObject();
42+
if (value.getId() != null) {
43+
gen.writeFieldName("id");
44+
gen.writeString(value.getId().toBase64UrlString());
45+
}
46+
if (value.getName() != null) {
47+
gen.writeFieldName("name");
48+
gen.writeString(value.getName());
49+
}
50+
if (value.getDisplayName() != null) {
51+
gen.writeFieldName("displayName");
52+
gen.writeString(value.getDisplayName());
53+
}
54+
gen.writeEndObject();
55+
}
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.webauthn.jackson;
18+
19+
import com.fasterxml.jackson.core.JsonParser;
20+
import com.fasterxml.jackson.databind.DeserializationContext;
21+
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
22+
import org.springframework.security.web.webauthn.api.ResidentKeyRequirement;
23+
24+
import java.io.IOException;
25+
26+
/**
27+
* Jackson deserializer for {@link ResidentKeyRequirement}
28+
*
29+
* @author Justin Cranford
30+
* @since 6.5
31+
*/
32+
@SuppressWarnings("serial")
33+
public class ResidentKeyRequirementDeserializer extends StdDeserializer<ResidentKeyRequirement> {
34+
35+
public ResidentKeyRequirementDeserializer() {
36+
super(ResidentKeyRequirement.class);
37+
}
38+
39+
@Override
40+
public ResidentKeyRequirement deserialize(JsonParser parser, DeserializationContext ctxt)
41+
throws IOException {
42+
String type = parser.readValueAs(String.class);
43+
return ResidentKeyRequirement.valueOf(type);
44+
}
45+
}

‎web/src/main/java/org/springframework/security/web/webauthn/jackson/ResidentKeyRequirementMixin.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,19 @@
1616

1717
package org.springframework.security.web.webauthn.jackson;
1818

19+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
1920
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
20-
2121
import org.springframework.security.web.webauthn.api.ResidentKeyRequirement;
2222

2323
/**
2424
* Jackson mixin for {@link ResidentKeyRequirement}
2525
*
2626
* @author Rob Winch
27+
* @author Justin Cranford
2728
* @since 6.4
2829
*/
2930
@JsonSerialize(using = ResidentKeyRequirementSerializer.class)
31+
@JsonDeserialize(using = ResidentKeyRequirementDeserializer.class)
3032
abstract class ResidentKeyRequirementMixin {
3133

3234
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.webauthn.jackson;
18+
19+
import com.fasterxml.jackson.core.JsonParser;
20+
import com.fasterxml.jackson.databind.DeserializationContext;
21+
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
22+
import org.springframework.security.web.webauthn.api.UserVerificationRequirement;
23+
24+
import java.io.IOException;
25+
26+
/**
27+
* Jackson deserializer for {@link UserVerificationRequirement}
28+
*
29+
* @author Justin Cranford
30+
* @since 6.5
31+
*/
32+
@SuppressWarnings("serial")
33+
public class UserVerificationRequirementDeserializer extends StdDeserializer<UserVerificationRequirement> {
34+
35+
public UserVerificationRequirementDeserializer() {
36+
super(UserVerificationRequirement.class);
37+
}
38+
39+
@Override
40+
public UserVerificationRequirement deserialize(JsonParser parser, DeserializationContext ctxt)
41+
throws IOException {
42+
String type = parser.readValueAs(String.class);
43+
return UserVerificationRequirement.valueOf(type);
44+
}
45+
}

‎web/src/main/java/org/springframework/security/web/webauthn/jackson/UserVerificationRequirementMixin.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,19 @@
1616

1717
package org.springframework.security.web.webauthn.jackson;
1818

19+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
1920
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
20-
2121
import org.springframework.security.web.webauthn.api.UserVerificationRequirement;
2222

2323
/**
2424
* Jackson mixin for {@link UserVerificationRequirement}
2525
*
2626
* @author Rob Winch
27+
* @author Justin Cranford
2728
* @since 6.4
2829
*/
2930
@JsonSerialize(using = UserVerificationRequirementSerializer.class)
31+
@JsonDeserialize(using = UserVerificationRequirementDeserializer.class)
3032
abstract class UserVerificationRequirementMixin {
3133

3234
}

‎web/src/main/java/org/springframework/security/web/webauthn/jackson/WebauthnJackson2Module.java

+10
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,12 @@
3434
import org.springframework.security.web.webauthn.api.CredentialPropertiesOutput;
3535
import org.springframework.security.web.webauthn.api.PublicKeyCredential;
3636
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
37+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialDescriptor;
38+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialParameters;
3739
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions;
40+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRpEntity;
3841
import org.springframework.security.web.webauthn.api.PublicKeyCredentialType;
42+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
3943
import org.springframework.security.web.webauthn.api.ResidentKeyRequirement;
4044
import org.springframework.security.web.webauthn.api.UserVerificationRequirement;
4145
import org.springframework.security.web.webauthn.management.RelyingPartyPublicKey;
@@ -92,6 +96,12 @@ public void setupModule(SetupContext context) {
9296
context.setMixInAnnotations(RelyingPartyPublicKey.class, RelyingPartyPublicKeyMixin.class);
9397
context.setMixInAnnotations(ResidentKeyRequirement.class, ResidentKeyRequirementMixin.class);
9498
context.setMixInAnnotations(UserVerificationRequirement.class, UserVerificationRequirementMixin.class);
99+
100+
context.setMixInAnnotations(PublicKeyCredentialUserEntity.class, PublicKeyCredentialUserEntityMixin.class);
101+
context.setMixInAnnotations(PublicKeyCredentialRpEntity.class, PublicKeyCredentialRpEntityMixin.class);
102+
context.setMixInAnnotations(PublicKeyCredentialParameters.class, PublicKeyCredentialParametersMixin.class);
103+
context.setMixInAnnotations(PublicKeyCredentialDescriptor.class, PublicKeyCredentialDescriptorMixin.class);
104+
95105
}
96106

97107
}

‎web/src/test/java/org/springframework/security/web/webauthn/jackson/JacksonTests.java

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import com.fasterxml.jackson.core.type.TypeReference;
2323
import com.fasterxml.jackson.databind.ObjectMapper;
24+
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
2425
import org.junit.jupiter.api.BeforeEach;
2526
import org.junit.jupiter.api.Test;
2627
import org.skyscreamer.jsonassert.JSONAssert;
@@ -52,6 +53,7 @@ class JacksonTests {
5253
void setup() {
5354
this.mapper = new ObjectMapper();
5455
this.mapper.registerModule(new WebauthnJackson2Module());
56+
this.mapper.registerModule(new JavaTimeModule());
5557
}
5658

5759
@Test
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.webauthn.jackson;
18+
19+
import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInput;
20+
21+
/**
22+
* Implements <a href=
23+
* "https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-minpinlength-extension">
24+
* Minimum PIN Length Extension (minPinLength)</a>.
25+
*
26+
* @author Justin Cranford
27+
* @since 6.5
28+
*/
29+
record MinPinLengthAuthenticationExtensionsClientInput(Boolean getInput) implements AuthenticationExtensionsClientInput<Boolean> {
30+
@Override
31+
public String getExtensionId() {
32+
return "minPinLength";
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package org.springframework.security.web.webauthn.jackson;
2+
3+
import org.springframework.security.web.webauthn.api.AttestationConveyancePreference;
4+
import org.springframework.security.web.webauthn.api.AuthenticatorAttachment;
5+
import org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria;
6+
import org.springframework.security.web.webauthn.api.AuthenticatorTransport;
7+
import org.springframework.security.web.webauthn.api.Bytes;
8+
import org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput;
9+
import org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput.CredProtect;
10+
import org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput.CredProtect.ProtectionPolicy;
11+
import org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInputs;
12+
import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity;
13+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
14+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialDescriptor;
15+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialParameters;
16+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRpEntity;
17+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialType;
18+
import org.springframework.security.web.webauthn.api.ResidentKeyRequirement;
19+
import org.springframework.security.web.webauthn.api.UserVerificationRequirement;
20+
21+
import java.time.Duration;
22+
import java.util.List;
23+
import java.util.Set;
24+
25+
/**
26+
* Object for {@code PublicKeyCredentialCreationOptions}
27+
*
28+
* @author Justin Cranford
29+
* @since 6.5
30+
*/
31+
public final class PublicKeyCredentialCreationOptionsGivens {
32+
private PublicKeyCredentialCreationOptionsGivens() {}
33+
34+
public static PublicKeyCredentialCreationOptions create() {
35+
return PublicKeyCredentialCreationOptions.builder()
36+
.rp(
37+
PublicKeyCredentialRpEntity.builder()
38+
.id("example.com")
39+
.name("Example RP")
40+
.build()
41+
)
42+
.user(
43+
ImmutablePublicKeyCredentialUserEntity.builder()
44+
.name("name")
45+
.id(Bytes.random())
46+
.displayName("displayName")
47+
.build()
48+
)
49+
.challenge(Bytes.random())
50+
.pubKeyCredParams(
51+
List.of(
52+
PublicKeyCredentialParameters.EdDSA,
53+
PublicKeyCredentialParameters.ES256,
54+
PublicKeyCredentialParameters.ES384,
55+
PublicKeyCredentialParameters.ES512,
56+
PublicKeyCredentialParameters.RS256,
57+
PublicKeyCredentialParameters.RS384,
58+
PublicKeyCredentialParameters.RS512,
59+
PublicKeyCredentialParameters.RS1
60+
)
61+
)
62+
.timeout(Duration.ofSeconds(60))
63+
.excludeCredentials(
64+
List.of(
65+
PublicKeyCredentialDescriptor.builder()
66+
.id(Bytes.random())
67+
.type(PublicKeyCredentialType.PUBLIC_KEY)
68+
.transports(Set.of(AuthenticatorTransport.USB))
69+
.build(),
70+
PublicKeyCredentialDescriptor.builder()
71+
.id(Bytes.random())
72+
.type(PublicKeyCredentialType.PUBLIC_KEY)
73+
.transports(Set.of(AuthenticatorTransport.NFC))
74+
.build(),
75+
PublicKeyCredentialDescriptor.builder()
76+
.id(Bytes.random())
77+
.type(PublicKeyCredentialType.PUBLIC_KEY)
78+
.transports(Set.of(AuthenticatorTransport.BLE))
79+
.build(),
80+
PublicKeyCredentialDescriptor.builder()
81+
.id(Bytes.random())
82+
.type(PublicKeyCredentialType.PUBLIC_KEY)
83+
.transports(Set.of(AuthenticatorTransport.SMART_CARD))
84+
.build(),
85+
PublicKeyCredentialDescriptor.builder()
86+
.id(Bytes.random())
87+
.type(PublicKeyCredentialType.PUBLIC_KEY)
88+
.transports(Set.of(AuthenticatorTransport.HYBRID))
89+
.build(),
90+
PublicKeyCredentialDescriptor.builder()
91+
.id(Bytes.random())
92+
.type(PublicKeyCredentialType.PUBLIC_KEY)
93+
.transports(Set.of(AuthenticatorTransport.INTERNAL))
94+
.build()
95+
)
96+
)
97+
.authenticatorSelection(
98+
AuthenticatorSelectionCriteria.builder()
99+
.userVerification(UserVerificationRequirement.PREFERRED)
100+
.residentKey(ResidentKeyRequirement.REQUIRED)
101+
.authenticatorAttachment(AuthenticatorAttachment.PLATFORM)
102+
.build()
103+
)
104+
.attestation(AttestationConveyancePreference.DIRECT)
105+
.extensions(
106+
new ImmutableAuthenticationExtensionsClientInputs(
107+
new CredProtectAuthenticationExtensionsClientInput(new CredProtect(ProtectionPolicy.USER_VERIFICATION_REQUIRED, true)),
108+
new MinPinLengthAuthenticationExtensionsClientInput(true)
109+
)
110+
)
111+
.build();
112+
}
113+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.webauthn.jackson;
18+
19+
import com.fasterxml.jackson.databind.ObjectMapper;
20+
import com.fasterxml.jackson.databind.SerializationFeature;
21+
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
22+
import org.junit.jupiter.api.BeforeEach;
23+
import org.junit.jupiter.api.Test;
24+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
25+
26+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
27+
import static org.junit.jupiter.api.Assertions.assertEquals;
28+
29+
/**
30+
* Test Jackson serialization and deserialization of PublicKeyCredentialCreationOptions
31+
*
32+
* @author Justin Cranford
33+
* @since 6.5
34+
*/
35+
class PublicKeyCredentialCreationOptionsTests {
36+
37+
private ObjectMapper mapper;
38+
39+
@BeforeEach
40+
void setup() {
41+
this.mapper = new ObjectMapper();
42+
this.mapper.enable(SerializationFeature.INDENT_OUTPUT);
43+
this.mapper.registerModule(new WebauthnJackson2Module());
44+
this.mapper.registerModule(new JavaTimeModule());
45+
}
46+
47+
@Test
48+
public void testSerializeDeserialize() {
49+
final PublicKeyCredentialCreationOptions given = PublicKeyCredentialCreationOptionsGivens.create();
50+
51+
final String serialized = assertDoesNotThrow(() -> this.mapper.writeValueAsString(given));
52+
//System.out.println("serialized:\n" + serialized + "\n\n");
53+
54+
final PublicKeyCredentialCreationOptions deserialized = assertDoesNotThrow(() -> this.mapper.readValue(serialized, PublicKeyCredentialCreationOptions.class));
55+
//System.out.println("deserialized:\n" + deserialized + "\n\n");
56+
57+
final String serializedAgain = assertDoesNotThrow(() -> this.mapper.writeValueAsString(deserialized));
58+
//System.out.println("serializedAgain:\n" + serializedAgain + "\n\n");
59+
60+
assertEquals(serialized, serializedAgain);
61+
}
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package org.springframework.security.web.webauthn.jackson;
2+
3+
import org.springframework.security.web.webauthn.api.AuthenticatorTransport;
4+
import org.springframework.security.web.webauthn.api.Bytes;
5+
import org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput;
6+
import org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput.CredProtect;
7+
import org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput.CredProtect.ProtectionPolicy;
8+
import org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInputs;
9+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialDescriptor;
10+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions;
11+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialType;
12+
import org.springframework.security.web.webauthn.api.UserVerificationRequirement;
13+
14+
import java.time.Duration;
15+
import java.util.List;
16+
import java.util.Set;
17+
18+
/**
19+
* Object for {@code PublicKeyCredentialRequestOptions}
20+
*
21+
* @author Justin Cranford
22+
* @since 6.5
23+
*/
24+
public final class PublicKeyCredentialRequestOptionsGivens {
25+
private PublicKeyCredentialRequestOptionsGivens() {}
26+
27+
public static PublicKeyCredentialRequestOptions create() {
28+
return PublicKeyCredentialRequestOptions.builder()
29+
.challenge(Bytes.random())
30+
.timeout(Duration.ofSeconds(60))
31+
.rpId("example.com")
32+
.allowCredentials(
33+
List.of(
34+
PublicKeyCredentialDescriptor.builder()
35+
.id(Bytes.random())
36+
.type(PublicKeyCredentialType.PUBLIC_KEY)
37+
.transports(Set.of(AuthenticatorTransport.USB))
38+
.build(),
39+
PublicKeyCredentialDescriptor.builder()
40+
.id(Bytes.random())
41+
.type(PublicKeyCredentialType.PUBLIC_KEY)
42+
.transports(Set.of(AuthenticatorTransport.NFC))
43+
.build(),
44+
PublicKeyCredentialDescriptor.builder()
45+
.id(Bytes.random())
46+
.type(PublicKeyCredentialType.PUBLIC_KEY)
47+
.transports(Set.of(AuthenticatorTransport.BLE))
48+
.build(),
49+
PublicKeyCredentialDescriptor.builder()
50+
.id(Bytes.random())
51+
.type(PublicKeyCredentialType.PUBLIC_KEY)
52+
.transports(Set.of(AuthenticatorTransport.SMART_CARD))
53+
.build(),
54+
PublicKeyCredentialDescriptor.builder()
55+
.id(Bytes.random())
56+
.type(PublicKeyCredentialType.PUBLIC_KEY)
57+
.transports(Set.of(AuthenticatorTransport.HYBRID))
58+
.build(),
59+
PublicKeyCredentialDescriptor.builder()
60+
.id(Bytes.random())
61+
.type(PublicKeyCredentialType.PUBLIC_KEY)
62+
.transports(Set.of(AuthenticatorTransport.INTERNAL))
63+
.build()
64+
)
65+
)
66+
.userVerification(UserVerificationRequirement.PREFERRED)
67+
.extensions(
68+
new ImmutableAuthenticationExtensionsClientInputs(
69+
new CredProtectAuthenticationExtensionsClientInput(new CredProtect(ProtectionPolicy.USER_VERIFICATION_REQUIRED, true)),
70+
new MinPinLengthAuthenticationExtensionsClientInput(true)
71+
)
72+
)
73+
.build();
74+
}
75+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.webauthn.jackson;
18+
19+
import com.fasterxml.jackson.databind.ObjectMapper;
20+
import com.fasterxml.jackson.databind.SerializationFeature;
21+
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
22+
import org.junit.jupiter.api.BeforeEach;
23+
import org.junit.jupiter.api.Test;
24+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions;
25+
26+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
27+
import static org.junit.jupiter.api.Assertions.assertEquals;
28+
29+
/**
30+
* Test Jackson serialization and deserialization of PublicKeyCredentialRequestOptions
31+
*
32+
* @author Justin Cranford
33+
* @since 6.5
34+
*/
35+
class PublicKeyCredentialRequestOptionsTests {
36+
37+
private ObjectMapper mapper;
38+
39+
@BeforeEach
40+
void setup() {
41+
this.mapper = new ObjectMapper();
42+
this.mapper.enable(SerializationFeature.INDENT_OUTPUT);
43+
this.mapper.registerModule(new WebauthnJackson2Module());
44+
this.mapper.registerModule(new JavaTimeModule());
45+
}
46+
47+
@Test
48+
public void testSerializeDeserialize() {
49+
final PublicKeyCredentialRequestOptions given = PublicKeyCredentialRequestOptionsGivens.create();
50+
51+
final String serialized = assertDoesNotThrow(() -> this.mapper.writeValueAsString(given));
52+
//System.out.println("serialized:\n" + serialized + "\n\n");
53+
54+
final PublicKeyCredentialRequestOptions deserialized = assertDoesNotThrow(() -> this.mapper.readValue(serialized, PublicKeyCredentialRequestOptions.class));
55+
//System.out.println("deserialized:\n" + deserialized + "\n\n");
56+
57+
final String serializedAgain = assertDoesNotThrow(() -> this.mapper.writeValueAsString(deserialized));
58+
//System.out.println("serializedAgain:\n" + serializedAgain + "\n\n");
59+
60+
assertEquals(serialized, serializedAgain);
61+
}
62+
}

0 commit comments

Comments
 (0)
Please sign in to comment.