Merge Make PublicKeyCredentialCreationOptions Serializable

Make PublicKeyCredentialCreationOptions Serializable
This commit is contained in:
Rob Winch 2026-02-23 16:01:34 -06:00 committed by GitHub
commit a4cadb5cc5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 123 additions and 8 deletions

View File

@ -20,6 +20,7 @@ import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.security.Principal;
import java.time.Duration;
import java.time.Instant;
import java.util.Collection;
import java.util.Date;
@ -245,12 +246,15 @@ import org.springframework.security.web.savedrequest.SimpleSavedRequest;
import org.springframework.security.web.server.firewall.ServerExchangeRejectedException;
import org.springframework.security.web.session.HttpSessionCreatedEvent;
import org.springframework.security.web.session.HttpSessionIdChangedEvent;
import org.springframework.security.web.webauthn.api.AttestationConveyancePreference;
import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInputs;
import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientOutputs;
import org.springframework.security.web.webauthn.api.AuthenticatorAssertionResponse;
import org.springframework.security.web.webauthn.api.AuthenticatorAttachment;
import org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria;
import org.springframework.security.web.webauthn.api.AuthenticatorTransport;
import org.springframework.security.web.webauthn.api.Bytes;
import org.springframework.security.web.webauthn.api.COSEAlgorithmIdentifier;
import org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput;
import org.springframework.security.web.webauthn.api.CredentialPropertiesOutput;
import org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInput;
@ -258,12 +262,17 @@ import org.springframework.security.web.webauthn.api.ImmutableAuthenticationExte
import org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientOutputs;
import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity;
import org.springframework.security.web.webauthn.api.PublicKeyCredential;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialDescriptor;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialParameters;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRpEntity;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialType;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
import org.springframework.security.web.webauthn.api.ResidentKeyRequirement;
import org.springframework.security.web.webauthn.api.TestAuthenticationAssertionResponses;
import org.springframework.security.web.webauthn.api.TestBytes;
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialCreationOptions;
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialRequestOptions;
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialUserEntities;
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentials;
@ -271,6 +280,7 @@ import org.springframework.security.web.webauthn.api.UserVerificationRequirement
import org.springframework.security.web.webauthn.authentication.WebAuthnAuthentication;
import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationRequestToken;
import org.springframework.security.web.webauthn.management.RelyingPartyAuthenticationRequest;
import org.springframework.security.web.webauthn.management.TestPublicKeyCredentialRpEntities;
import org.springframework.util.ReflectionUtils;
final class SerializationSamples {
@ -879,6 +889,36 @@ final class SerializationSamples {
generatorByClassName.put(CredentialPropertiesOutput.ExtensionOutput.class,
(r) -> new CredentialPropertiesOutput(true).getOutput());
AttestationConveyancePreference attestationConveyancePreference = AttestationConveyancePreference.DIRECT;
ResidentKeyRequirement residentKeyRequirement = ResidentKeyRequirement.REQUIRED;
AuthenticatorSelectionCriteria authenticatorSelectionCriteria = AuthenticatorSelectionCriteria.builder()
.authenticatorAttachment(AuthenticatorAttachment.PLATFORM)
.residentKey(residentKeyRequirement)
.userVerification(UserVerificationRequirement.REQUIRED)
.build();
PublicKeyCredentialParameters publicKeyCredentialParameters = PublicKeyCredentialParameters.RS256;
PublicKeyCredentialRpEntity publicKeyCredentialRpEntity = TestPublicKeyCredentialRpEntities.createRpEntity()
.build();
generatorByClassName.put(AttestationConveyancePreference.class, (r) -> attestationConveyancePreference);
generatorByClassName.put(ResidentKeyRequirement.class, (r) -> residentKeyRequirement);
generatorByClassName.put(AuthenticatorSelectionCriteria.class, (r) -> authenticatorSelectionCriteria);
generatorByClassName.put(COSEAlgorithmIdentifier.class, ((r) -> COSEAlgorithmIdentifier.RS256));
generatorByClassName.put(PublicKeyCredentialParameters.class, (r) -> publicKeyCredentialParameters);
generatorByClassName.put(PublicKeyCredentialRpEntity.class, (r) -> publicKeyCredentialRpEntity);
generatorByClassName.put(PublicKeyCredentialCreationOptions.class,
(o) -> TestPublicKeyCredentialCreationOptions.createPublicKeyCredentialCreationOptions()
.extensions(inputs)
.attestation(attestationConveyancePreference)
.authenticatorSelection(authenticatorSelectionCriteria)
.challenge(TestBytes.get())
.excludeCredentials(List.of(descriptor))
.rp(publicKeyCredentialRpEntity)
.pubKeyCredParams(publicKeyCredentialParameters)
.timeout(Duration.ofMinutes(5))
.user(TestPublicKeyCredentialUserEntities.userEntity().id(TestBytes.get()).build())
.build());
// One-Time Token
DefaultOneTimeToken oneTimeToken = new DefaultOneTimeToken(UUID.randomUUID().toString(), "user",
Instant.now().plusSeconds(300));

View File

@ -16,6 +16,9 @@
package org.springframework.security.web.webauthn.api;
import java.io.Serial;
import java.io.Serializable;
/**
* <a href="https://www.w3.org/TR/webauthn-3/#webauthn-relying-party">WebAuthn Relying
* Parties</a> may use <a href=
@ -26,7 +29,10 @@ package org.springframework.security.web.webauthn.api;
* @author Rob Winch
* @since 6.4
*/
public final class AttestationConveyancePreference {
public final class AttestationConveyancePreference implements Serializable {
@Serial
private static final long serialVersionUID = -4252430175801658788L;
/**
* The <a href=

View File

@ -16,6 +16,9 @@
package org.springframework.security.web.webauthn.api;
import java.io.Serial;
import java.io.Serializable;
import org.jspecify.annotations.Nullable;
/**
@ -33,7 +36,10 @@ import org.jspecify.annotations.Nullable;
* @since 6.4
* @see PublicKeyCredentialCreationOptions#getAuthenticatorSelection()
*/
public final class AuthenticatorSelectionCriteria {
public final class AuthenticatorSelectionCriteria implements Serializable {
@Serial
private static final long serialVersionUID = -5923595063546985635L;
private final @Nullable AuthenticatorAttachment authenticatorAttachment;

View File

@ -16,6 +16,9 @@
package org.springframework.security.web.webauthn.api;
import java.io.Serial;
import java.io.Serializable;
/**
* <a href=
* "https://www.w3.org/TR/webauthn-3/#sctn-alg-identifier">COSEAlgorithmIdentifier</a> is
@ -25,7 +28,10 @@ package org.springframework.security.web.webauthn.api;
* @since 6.4
* @see PublicKeyCredentialParameters#getAlg()
*/
public final class COSEAlgorithmIdentifier {
public final class COSEAlgorithmIdentifier implements Serializable {
@Serial
private static final long serialVersionUID = -4907140472964185350L;
public static final COSEAlgorithmIdentifier EdDSA = new COSEAlgorithmIdentifier(-8);

View File

@ -16,6 +16,8 @@
package org.springframework.security.web.webauthn.api;
import java.io.Serial;
import java.io.Serializable;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
@ -34,7 +36,10 @@ import org.jspecify.annotations.Nullable;
* @author Rob Winch
* @since 6.4
*/
public final class PublicKeyCredentialCreationOptions {
public final class PublicKeyCredentialCreationOptions implements Serializable {
@Serial
private static final long serialVersionUID = 800506727675279143L;
private final PublicKeyCredentialRpEntity rp;

View File

@ -16,6 +16,9 @@
package org.springframework.security.web.webauthn.api;
import java.io.Serial;
import java.io.Serializable;
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialparameters">PublicKeyCredentialParameters</a>
@ -25,7 +28,10 @@ package org.springframework.security.web.webauthn.api;
* @since 6.4
* @see PublicKeyCredentialCreationOptions#getPubKeyCredParams()
*/
public final class PublicKeyCredentialParameters {
public final class PublicKeyCredentialParameters implements Serializable {
@Serial
private static final long serialVersionUID = -623985171385991L;
public static final PublicKeyCredentialParameters EdDSA = new PublicKeyCredentialParameters(
COSEAlgorithmIdentifier.EdDSA);

View File

@ -16,6 +16,9 @@
package org.springframework.security.web.webauthn.api;
import java.io.Serial;
import java.io.Serializable;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
@ -29,7 +32,10 @@ import org.springframework.util.Assert;
* @author Rob Winch
* @since 6.4
*/
public final class PublicKeyCredentialRpEntity {
public final class PublicKeyCredentialRpEntity implements Serializable {
@Serial
private static final long serialVersionUID = -2741017471467698174L;
private final String name;

View File

@ -16,6 +16,9 @@
package org.springframework.security.web.webauthn.api;
import java.io.Serial;
import java.io.Serializable;
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#enumdef-residentkeyrequirement">ResidentKeyRequirement</a>
@ -24,7 +27,10 @@ package org.springframework.security.web.webauthn.api;
* @author Rob Winch
* @since 6.4
*/
public final class ResidentKeyRequirement {
public final class ResidentKeyRequirement implements Serializable {
@Serial
private static final long serialVersionUID = 1730700608982834858L;
/**
* The <a href=

View File

@ -317,7 +317,8 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe
private com.webauthn4j.data.PublicKeyCredentialParameters convertParamToWebauthn4j(
PublicKeyCredentialParameters parameter) {
if (parameter.getType() != PublicKeyCredentialType.PUBLIC_KEY) {
PublicKeyCredentialType credentialType = PublicKeyCredentialType.valueOf(parameter.getType().getValue());
if (credentialType != PublicKeyCredentialType.PUBLIC_KEY) {
throw new IllegalArgumentException(
"Cannot convert unknown credential type " + parameter.getType() + " to webauthn4j");
}

View File

@ -16,6 +16,11 @@
package org.springframework.security.web.webauthn.management;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Arrays;
@ -77,6 +82,7 @@ import org.springframework.security.web.webauthn.api.UserVerificationRequirement
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.assertj.core.api.Assertions.assertThatRuntimeException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
@ -261,6 +267,33 @@ class Webauthn4jRelyingPartyOperationsTests {
AuthenticatorTransport.HYBRID);
}
@Test
void registerCredentialWhenCreationOptionsAreJavaDeserializedThenDoesNotThrow()
throws IOException, ClassNotFoundException {
PublicKeyCredentialCreationOptions creationOptions = TestPublicKeyCredentialCreationOptions
.createPublicKeyCredentialCreationOptions()
.build();
PublicKeyCredential<AuthenticatorAttestationResponse> publicKeyCredential = TestPublicKeyCredentials
.createPublicKeyCredential()
.build();
RelyingPartyPublicKey rpPublicKey = new RelyingPartyPublicKey(publicKeyCredential, this.label);
try (ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(out)) {
objectOutputStream.writeObject(creationOptions);
objectOutputStream.flush();
try (ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(in)) {
PublicKeyCredentialCreationOptions deserialized = (PublicKeyCredentialCreationOptions) objectInputStream
.readObject();
ImmutableRelyingPartyRegistrationRequest rpRegistrationRequest = new ImmutableRelyingPartyRegistrationRequest(
deserialized, rpPublicKey);
assertThatNoException().isThrownBy(() -> this.rpOperations.registerCredential(rpRegistrationRequest));
}
}
}
@Test
void registerCredentialWhenInternalTransportThenCredentialRecordHasTransport() {
PublicKeyCredentialCreationOptions creationOptions = TestPublicKeyCredentialCreationOptions