1
0
mirror of synced 2026-05-22 21:33:16 +00:00

Extract spring-security-webauthn

Closes gh-17586
This commit is contained in:
Rob Winch
2025-07-22 17:18:38 -05:00
parent 7c887d2da1
commit 79cd982341
129 changed files with 45 additions and 2 deletions
@@ -1,103 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
/**
* <a href="https://www.w3.org/TR/webauthn-3/#webauthn-relying-party">WebAuthn Relying
* Parties</a> may use <a href=
* "https://www.w3.org/TR/webauthn-3/#enumdef-attestationconveyancepreference">AttestationConveyancePreference</a>
* to specify their preference regarding attestation conveyance during credential
* generation.
*
* @author Rob Winch
* @since 6.4
*/
public final class AttestationConveyancePreference {
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-attestationconveyancepreference-none">none</a>
* preference indicates that the Relying Party is not interested in
* <a href="https://www.w3.org/TR/webauthn-3/#authenticator">authenticator</a>
* <a href="https://www.w3.org/TR/webauthn-3/#attestation">attestation</a>.
*/
public static final AttestationConveyancePreference NONE = new AttestationConveyancePreference("none");
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-attestationconveyancepreference-indirect">indirect</a>
* preference indicates that the Relying Party wants to receive a verifiable
* <a href="https://www.w3.org/TR/webauthn-3/#attestation-statement">attestation
* statement</a>, but allows the client to decide how to obtain such an attestation
* statement.
*/
public static final AttestationConveyancePreference INDIRECT = new AttestationConveyancePreference("indirect");
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-attestationconveyancepreference-direct">direct</a>
* preference indicates that the Relying Party wants to receive the
* <a href="https://www.w3.org/TR/webauthn-3/#attestation-statement">attestation
* statement</a> as generated by the
* <a href="https://www.w3.org/TR/webauthn-3/#authenticator">authenticator</a>.
*/
public static final AttestationConveyancePreference DIRECT = new AttestationConveyancePreference("direct");
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-attestationconveyancepreference-enterprise">enterprise</a>
* preference indicates that the Relying Party wants to receive an
* <a href="https://www.w3.org/TR/webauthn-3/#attestation-statement">attestation
* statement</a> that may include uniquely identifying information.
*/
public static final AttestationConveyancePreference ENTERPRISE = new AttestationConveyancePreference("enterprise");
private final String value;
AttestationConveyancePreference(String value) {
this.value = value;
}
/**
* Gets the String value of the preference.
* @return the String value of the preference.
*/
public String getValue() {
return this.value;
}
/**
* Gets an instance of {@link AttestationConveyancePreference}
* @param value the {@link #getValue()}
* @return an {@link AttestationConveyancePreference}
*/
public static AttestationConveyancePreference valueOf(String value) {
switch (value) {
case "none":
return NONE;
case "indirect":
return INDIRECT;
case "direct":
return DIRECT;
case "enterprise":
return ENTERPRISE;
default:
return new AttestationConveyancePreference(value);
}
}
}
@@ -1,50 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
import java.io.Serializable;
/**
* A <a href="https://www.w3.org/TR/webauthn-3/#client-extension-input">client extension
* input</a> entry in the {@link AuthenticationExtensionsClientInputs}.
*
* @param <T>
* @author Rob Winch
* @since 6.4
* @see ImmutableAuthenticationExtensionsClientInput
*/
public interface AuthenticationExtensionsClientInput<T> extends Serializable {
/**
* Gets the <a href="https://www.w3.org/TR/webauthn-3/#extension-identifier">extension
* identifier</a>.
* @return the
* <a href="https://www.w3.org/TR/webauthn-3/#extension-identifier">extension
* identifier</a>.
*/
String getExtensionId();
/**
* Gets the <a href="https://www.w3.org/TR/webauthn-3/#client-extension-input">client
* extension</a>.
* @return the
* <a href="https://www.w3.org/TR/webauthn-3/#client-extension-input">client
* extension</a>.
*/
T getInput();
}
@@ -1,43 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
import java.io.Serializable;
import java.util.List;
/**
* <a href=
* "https://www.w3.org/TR/webauthn-3/#iface-authentication-extensions-client-inputs">AuthenticationExtensionsClientInputs</a>
* is a dictionary containing the
* <a href="https://www.w3.org/TR/webauthn-3/#client-extension-input">client extension
* input</a> values for zero or more
* <a href="https://www.w3.org/TR/webauthn-3/#webauthn-extensions">WebAuthn
* Extensions</a>.
*
* @author Rob Winch
* @since 6.4
* @see PublicKeyCredentialCreationOptions#getExtensions()
*/
public interface AuthenticationExtensionsClientInputs extends Serializable {
/**
* Gets all of the {@link AuthenticationExtensionsClientInput}.
* @return a non-null {@link List} of {@link AuthenticationExtensionsClientInput}.
*/
List<AuthenticationExtensionsClientInput> getInputs();
}
@@ -1,49 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
import java.io.Serializable;
/**
* A <a href="https://www.w3.org/TR/webauthn-3/#client-extension-output">client extension
* output</a> entry in {@link AuthenticationExtensionsClientOutputs}.
*
* @param <T>
* @see AuthenticationExtensionsClientOutputs#getOutputs()
* @see CredentialPropertiesOutput
*/
public interface AuthenticationExtensionsClientOutput<T> extends Serializable {
/**
* Gets the <a href="https://www.w3.org/TR/webauthn-3/#extension-identifier">extension
* identifier</a>.
* @return the
* <a href="https://www.w3.org/TR/webauthn-3/#extension-identifier">extension
* identifier</a>.
*/
String getExtensionId();
/**
* The <a href="https://www.w3.org/TR/webauthn-3/#client-extension-output">client
* extension output</a>.
* @return the
* <a href="https://www.w3.org/TR/webauthn-3/#client-extension-output">client
* extension output</a>.
*/
T getOutput();
}
@@ -1,43 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
import java.io.Serializable;
import java.util.List;
/**
* <a href=
* "https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsclientoutputs">AuthenticationExtensionsClientOutputs</a>
* is a dictionary containing the
* <a href="https://www.w3.org/TR/webauthn-3/#client-extension-output">client extension
* output</a> values for zero or more
* <a href="https://www.w3.org/TR/webauthn-3/#webauthn-extensions">WebAuthn
* Extensions</a>.
*
* @author Rob Winch
* @since 6.4
* @see PublicKeyCredential#getClientExtensionResults()
*/
public interface AuthenticationExtensionsClientOutputs extends Serializable {
/**
* Gets all of the {@link AuthenticationExtensionsClientOutput}.
* @return a non-null {@link List} of {@link AuthenticationExtensionsClientOutput}.
*/
List<AuthenticationExtensionsClientOutput<?>> getOutputs();
}
@@ -1,210 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
import java.io.Serial;
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#authenticatorassertionresponse">AuthenticatorAssertionResponse</a>
* interface represents an
* <a href="https://www.w3.org/TR/webauthn-3/#authenticator">authenticator</a>'s response
* to a client's request for generation of a new
* <a href="https://www.w3.org/TR/webauthn-3/#authentication-assertion">authentication
* assertion</a> given the
* <a href="https://www.w3.org/TR/webauthn-3/#webauthn-relying-party">WebAuthn Relying
* Party</a>'s challenge and OPTIONAL list of credentials it is aware of. This response
* contains a cryptographic signature proving possession of the
* <a href="https://www.w3.org/TR/webauthn-3/#credential-private-key">credential private
* key</a>, and optionally evidence of
* <a href="https://www.w3.org/TR/webauthn-3/#user-consent">user consent</a> to a specific
* transaction.
*
* @author Rob Winch
* @since 6.4
* @see PublicKeyCredential#getResponse()
*/
public final class AuthenticatorAssertionResponse extends AuthenticatorResponse {
@Serial
private static final long serialVersionUID = 324976481675434298L;
private final Bytes authenticatorData;
private final Bytes signature;
private final Bytes userHandle;
private final Bytes attestationObject;
private AuthenticatorAssertionResponse(Bytes clientDataJSON, Bytes authenticatorData, Bytes signature,
Bytes userHandle, Bytes attestationObject) {
super(clientDataJSON);
this.authenticatorData = authenticatorData;
this.signature = signature;
this.userHandle = userHandle;
this.attestationObject = attestationObject;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-authenticatorassertionresponse-authenticatordata">authenticatorData</a>
* contains the
* <a href="https://www.w3.org/TR/webauthn-3/#authenticator-data">authenticator
* data</a> returned by the authenticator. See
* <a href="https://www.w3.org/TR/webauthn-3/#sctn-authenticator-data">6.1
* Authenticator Data.</a>.
* @return the {@code authenticatorData}
*/
public Bytes getAuthenticatorData() {
return this.authenticatorData;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-authenticatorassertionresponse-signature">signature</a>
* contains the raw signature returned from the authenticator. See
* <a href="https://www.w3.org/TR/webauthn-3/#sctn-op-get-assertion">6.3.3 The
* authenticatorGetAssertion Operation</a>.
* @return the {@code signature}
*/
public Bytes getSignature() {
return this.signature;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-authenticatorassertionresponse-userhandle">userHandle</a>
* is the <a href="https://www.w3.org/TR/webauthn-3/#user-handle">user handle</a>
* which is returned from the authenticator, or null if the authenticator did not
* return a user handle. See
* <a href="https://www.w3.org/TR/webauthn-3/#sctn-op-get-assertion">6.3.3 The
* authenticatorGetAssertion Operation</a>. The authenticator MUST always return a
* user handle if the <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-allowcredentials">allowCredentials</a>
* option used in the
* <a href="https://www.w3.org/TR/webauthn-3/#authentication-ceremony">authentication
* ceremony</a> is empty, and MAY return one otherwise.
* @return the <a href="https://www.w3.org/TR/webauthn-3/#user-handle">user handle</a>
*/
public Bytes getUserHandle() {
return this.userHandle;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-authenticatorattestationresponse-attestationobject">attestationObject</a>
* is an OPTIONAL attribute contains an
* <a href="https://www.w3.org/TR/webauthn-3/#attestation-object">attestation
* object</a>, if the authenticator supports attestation in assertions.
* @return the {@code attestationObject}
*/
public Bytes getAttestationObject() {
return this.attestationObject;
}
/**
* Creates a new {@link AuthenticatorAssertionResponseBuilder}
* @return the {@link AuthenticatorAssertionResponseBuilder}
*/
public static AuthenticatorAssertionResponseBuilder builder() {
return new AuthenticatorAssertionResponseBuilder();
}
/**
* Builds a {@link AuthenticatorAssertionResponse}.
*
* @author Rob Winch
* @since 6.4
*/
public static final class AuthenticatorAssertionResponseBuilder {
private Bytes authenticatorData;
private Bytes signature;
private Bytes userHandle;
private Bytes attestationObject;
private Bytes clientDataJSON;
private AuthenticatorAssertionResponseBuilder() {
}
/**
* Set the {@link #getAuthenticatorData()} property
* @param authenticatorData the authenticator data.
* @return the {@link AuthenticatorAssertionResponseBuilder}
*/
public AuthenticatorAssertionResponseBuilder authenticatorData(Bytes authenticatorData) {
this.authenticatorData = authenticatorData;
return this;
}
/**
* Set the {@link #getSignature()} property
* @param signature the signature
* @return the {@link AuthenticatorAssertionResponseBuilder}
*/
public AuthenticatorAssertionResponseBuilder signature(Bytes signature) {
this.signature = signature;
return this;
}
/**
* Set the {@link #getUserHandle()} property
* @param userHandle the user handle
* @return the {@link AuthenticatorAssertionResponseBuilder}
*/
public AuthenticatorAssertionResponseBuilder userHandle(Bytes userHandle) {
this.userHandle = userHandle;
return this;
}
/**
* Set the {@link #attestationObject} property
* @param attestationObject the attestation object
* @return the {@link AuthenticatorAssertionResponseBuilder}
*/
public AuthenticatorAssertionResponseBuilder attestationObject(Bytes attestationObject) {
this.attestationObject = attestationObject;
return this;
}
/**
* Set the {@link #getClientDataJSON()} property
* @param clientDataJSON the client data JSON
* @return the {@link AuthenticatorAssertionResponseBuilder}
*/
public AuthenticatorAssertionResponseBuilder clientDataJSON(Bytes clientDataJSON) {
this.clientDataJSON = clientDataJSON;
return this;
}
/**
* Builds the {@link AuthenticatorAssertionResponse}
* @return the {@link AuthenticatorAssertionResponse}
*/
public AuthenticatorAssertionResponse build() {
return new AuthenticatorAssertionResponse(this.clientDataJSON, this.authenticatorData, this.signature,
this.userHandle, this.attestationObject);
}
}
}
@@ -1,100 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
import java.io.ObjectStreamException;
import java.io.Serial;
import java.io.Serializable;
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#enumdef-authenticatorattachment">AuthenticatorAttachment</a>.
*
* @author Rob Winch
* @since 6.4
*/
public final class AuthenticatorAttachment implements Serializable {
@Serial
private static final long serialVersionUID = 8446133215195918090L;
/**
* Indicates <a href=
* "https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#cross-platform-attachment">cross-platform
* attachment</a>.
*
* <p>
* Authenticators of this class are removable from, and can "roam" among, client
* platforms.
*/
public static final AuthenticatorAttachment CROSS_PLATFORM = new AuthenticatorAttachment("cross-platform");
/**
* Indicates <a href=
* "https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#platform-attachment">platform
* attachment</a>.
*
* <p>
* Usually, authenticators of this class are not removable from the platform.
*/
public static final AuthenticatorAttachment PLATFORM = new AuthenticatorAttachment("platform");
private final String value;
AuthenticatorAttachment(String value) {
this.value = value;
}
/**
* Gets the value.
* @return the value.
*/
public String getValue() {
return this.value;
}
@Override
public String toString() {
return "AuthenticatorAttachment [" + this.value + "]";
}
/**
* Gets an instance of {@link AuthenticatorAttachment} based upon the value passed in.
* @param value the value to obtain the {@link AuthenticatorAttachment}
* @return the {@link AuthenticatorAttachment}
*/
public static AuthenticatorAttachment valueOf(String value) {
switch (value) {
case "cross-platform":
return CROSS_PLATFORM;
case "platform":
return PLATFORM;
default:
return new AuthenticatorAttachment(value);
}
}
public static AuthenticatorAttachment[] values() {
return new AuthenticatorAttachment[] { CROSS_PLATFORM, PLATFORM };
}
@Serial
private Object readResolve() throws ObjectStreamException {
return valueOf(this.value);
}
}
@@ -1,144 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
import java.util.Arrays;
import java.util.List;
/**
* <a href=
* "https://www.w3.org/TR/webauthn-3/#authenticatorattestationresponse">AuthenticatorAttestationResponse</a>
* represents the
* <a href="https://www.w3.org/TR/webauthn-3/#authenticator">authenticator</a>'s response
* to a client's request for the creation of a new
* <a href="https://www.w3.org/TR/webauthn-3/#public-key-credential">public key
* credential</a>.
*
* @author Rob Winch
* @since 6.4
* @see PublicKeyCredential#getResponse()
*/
public final class AuthenticatorAttestationResponse extends AuthenticatorResponse {
private final Bytes attestationObject;
private final List<AuthenticatorTransport> transports;
private AuthenticatorAttestationResponse(Bytes clientDataJSON, Bytes attestationObject,
List<AuthenticatorTransport> transports) {
super(clientDataJSON);
this.attestationObject = attestationObject;
this.transports = transports;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-authenticatorattestationresponse-attestationobject">attestationObject</a>
* attribute contains an attestation object, which is opaque to, and cryptographically
* protected against tampering by, the client.
* @return the attestationObject
*/
public Bytes getAttestationObject() {
return this.attestationObject;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-authenticatorattestationresponse-gettransports">transports</a>
* returns the <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-authenticatorattestationresponse-transports-slot">transports</a>
* @return the transports
*/
public List<AuthenticatorTransport> getTransports() {
return this.transports;
}
/**
* Creates a new instance.
* @return the {@link AuthenticatorAttestationResponseBuilder}
*/
public static AuthenticatorAttestationResponseBuilder builder() {
return new AuthenticatorAttestationResponseBuilder();
}
/**
* Builds {@link AuthenticatorAssertionResponse}.
*
* @author Rob Winch
* @since 6.4
*/
public static final class AuthenticatorAttestationResponseBuilder {
private Bytes attestationObject;
private List<AuthenticatorTransport> transports;
private Bytes clientDataJSON;
private AuthenticatorAttestationResponseBuilder() {
}
/**
* Sets the {@link #getAttestationObject()} property.
* @param attestationObject the attestation object.
* @return the {@link AuthenticatorAttestationResponseBuilder}
*/
public AuthenticatorAttestationResponseBuilder attestationObject(Bytes attestationObject) {
this.attestationObject = attestationObject;
return this;
}
/**
* Sets the {@link #getTransports()} property.
* @param transports the transports
* @return the {@link AuthenticatorAttestationResponseBuilder}
*/
public AuthenticatorAttestationResponseBuilder transports(AuthenticatorTransport... transports) {
return transports(Arrays.asList(transports));
}
/**
* Sets the {@link #getTransports()} property.
* @param transports the transports
* @return the {@link AuthenticatorAttestationResponseBuilder}
*/
public AuthenticatorAttestationResponseBuilder transports(List<AuthenticatorTransport> transports) {
this.transports = transports;
return this;
}
/**
* Sets the {@link #getClientDataJSON()} property.
* @param clientDataJSON the client data JSON.
* @return the {@link AuthenticatorAttestationResponseBuilder}
*/
public AuthenticatorAttestationResponseBuilder clientDataJSON(Bytes clientDataJSON) {
this.clientDataJSON = clientDataJSON;
return this;
}
/**
* Builds a {@link AuthenticatorAssertionResponse}.
* @return the {@link AuthenticatorAttestationResponseBuilder}
*/
public AuthenticatorAttestationResponse build() {
return new AuthenticatorAttestationResponse(this.clientDataJSON, this.attestationObject, this.transports);
}
}
}
@@ -1,55 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
import java.io.Serializable;
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#iface-authenticatorresponse">AuthenticatorResponse</a>
* represents <a href="https://www.w3.org/TR/webauthn-3/#authenticator">Authenticators</a>
* respond to <a href="https://www.w3.org/TR/webauthn-3/#relying-party">Relying Party</a>
* requests.
*
* @author Rob Winch
* @since 6.4
*/
public abstract class AuthenticatorResponse implements Serializable {
private final Bytes clientDataJSON;
/**
* Creates a new instance
* @param clientDataJSON the {@link #getClientDataJSON()}
*/
AuthenticatorResponse(Bytes clientDataJSON) {
this.clientDataJSON = clientDataJSON;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-authenticatorresponse-clientdatajson">clientDataJSON</a>
* contains a JSON-compatible serialization of the client data, the hash of which is
* passed to the authenticator by the client in its call to either create() or get()
* (i.e., the client data itself is not sent to the authenticator).
* @return the client data JSON
*/
public Bytes getClientDataJSON() {
return this.clientDataJSON;
}
}
@@ -1,170 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
/**
* <a href=
* "https://www.w3.org/TR/webauthn-3/#dictdef-authenticatorselectioncriteria">AuthenticatorAttachment</a>
* can be used by
* <a href="https://www.w3.org/TR/webauthn-3/#webauthn-relying-party">WebAuthn Relying
* Parties</a> to specify their requirements regarding authenticator attributes.
*
* There is no <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-authenticatorselectioncriteria-requireresidentkey">requireResidentKey</a>
* property because it is only for backwards compatibility with WebAuthn Level 1.
*
* @author Rob Winch
* @since 6.4
* @see PublicKeyCredentialCreationOptions#getAuthenticatorSelection()
*/
public final class AuthenticatorSelectionCriteria {
private final AuthenticatorAttachment authenticatorAttachment;
private final ResidentKeyRequirement residentKey;
private final UserVerificationRequirement userVerification;
// NOTE: There is no requireResidentKey property because it is only for backward
// compatibility with WebAuthn Level 1
/**
* Creates a new instance
* @param authenticatorAttachment the authenticator attachment
* @param residentKey the resident key requirement
* @param userVerification the user verification
*/
private AuthenticatorSelectionCriteria(AuthenticatorAttachment authenticatorAttachment,
ResidentKeyRequirement residentKey, UserVerificationRequirement userVerification) {
this.authenticatorAttachment = authenticatorAttachment;
this.residentKey = residentKey;
this.userVerification = userVerification;
}
/**
* If <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-authenticatorselectioncriteria-authenticatorattachment">
* authenticatorAttachment</a> is present, eligible
* <a href="https://www.w3.org/TR/webauthn-3/#authenticator">authenticators</a> are
* filtered to be only those authenticators attached with the specified
* <a href="https://www.w3.org/TR/webauthn-3/#enum-attachment">authenticator
* attachment modality</a> (see also <a href=
* "https://www.w3.org/TR/webauthn-3/#sctn-authenticator-attachment-modality">6.2.1
* Authenticator Attachment Modality</a>).
* @return the authenticator attachment
*/
public AuthenticatorAttachment getAuthenticatorAttachment() {
return this.authenticatorAttachment;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-authenticatorselectioncriteria-residentkey">residentKey</a>
* specifies the extent to which the
* <a href="https://www.w3.org/TR/webauthn-3/#relying-party">Relying Party</a> desires
* to create a <a href=
* "https://www.w3.org/TR/webauthn-3/#client-side-discoverable-credential">client-side
* discoverable credential</a>.
* @return the resident key requirement
*/
public ResidentKeyRequirement getResidentKey() {
return this.residentKey;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-authenticatorselectioncriteria-userverification">userVerification</a>
* specifies the <a href="https://www.w3.org/TR/webauthn-3/#relying-party">Relying
* Party</a>'s requirements regarding
* <a href="https://www.w3.org/TR/webauthn-3/#user-verification">user verification</a>
* for the <a href=
* "https://w3c.github.io/webappsec-credential-management/#dom-credentialscontainer-create">create()</a>
* operation.
* @return the user verification requirement
*/
public UserVerificationRequirement getUserVerification() {
return this.userVerification;
}
/**
* Creates a new {@link AuthenticatorSelectionCriteriaBuilder}
* @return a new {@link AuthenticatorSelectionCriteriaBuilder}
*/
public static AuthenticatorSelectionCriteriaBuilder builder() {
return new AuthenticatorSelectionCriteriaBuilder();
}
/**
* Creates a {@link AuthenticatorSelectionCriteria}
*
* @author Rob Winch
* @since 6.4
*/
public static final class AuthenticatorSelectionCriteriaBuilder {
private AuthenticatorAttachment authenticatorAttachment;
private ResidentKeyRequirement residentKey;
private UserVerificationRequirement userVerification;
private AuthenticatorSelectionCriteriaBuilder() {
}
/**
* Sets the {@link #getAuthenticatorAttachment()} property.
* @param authenticatorAttachment the authenticator attachment
* @return the {@link AuthenticatorSelectionCriteriaBuilder}
*/
public AuthenticatorSelectionCriteriaBuilder authenticatorAttachment(
AuthenticatorAttachment authenticatorAttachment) {
this.authenticatorAttachment = authenticatorAttachment;
return this;
}
/**
* Sets the {@link #getResidentKey()} property.
* @param residentKey the resident key
* @return the {@link AuthenticatorSelectionCriteriaBuilder}
*/
public AuthenticatorSelectionCriteriaBuilder residentKey(ResidentKeyRequirement residentKey) {
this.residentKey = residentKey;
return this;
}
/**
* Sets the {@link #getUserVerification()} property.
* @param userVerification the user verification requirement
* @return the {@link AuthenticatorSelectionCriteriaBuilder}
*/
public AuthenticatorSelectionCriteriaBuilder userVerification(UserVerificationRequirement userVerification) {
this.userVerification = userVerification;
return this;
}
/**
* Builds a {@link AuthenticatorSelectionCriteria}
* @return a new {@link AuthenticatorSelectionCriteria}
*/
public AuthenticatorSelectionCriteria build() {
return new AuthenticatorSelectionCriteria(this.authenticatorAttachment, this.residentKey,
this.userVerification);
}
}
}
@@ -1,124 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
import java.io.Serial;
import java.io.Serializable;
/**
* <a href=
* "https://www.w3.org/TR/webauthn-3/#enumdef-authenticatortransport">AuthenticatorTransport</a>
* defines hints as to how clients might communicate with a particular authenticator in
* order to obtain an assertion for a specific credential.
*
* @author Rob Winch
* @since 6.4
*/
public final class AuthenticatorTransport implements Serializable {
@Serial
private static final long serialVersionUID = -5617945441117386982L;
/**
* <a href="https://www.w3.org/TR/webauthn-3/#dom-authenticatortransport-usb">usbc</a>
* indicates the respective authenticator can be contacted over removable USB.
*/
public static final AuthenticatorTransport USB = new AuthenticatorTransport("usb");
/**
* <a href="https://www.w3.org/TR/webauthn-3/#dom-authenticatortransport-nfc">nfc</a>
* indicates the respective authenticator can be contacted over Near Field
* Communication (NFC).
*/
public static final AuthenticatorTransport NFC = new AuthenticatorTransport("nfc");
/**
* <a href="https://www.w3.org/TR/webauthn-3/#dom-authenticatortransport-ble">ble</a>
* Indicates the respective authenticator can be contacted over Bluetooth Smart
* (Bluetooth Low Energy / BLE).
*/
public static final AuthenticatorTransport BLE = new AuthenticatorTransport("ble");
/**
* <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-authenticatortransport-smart-card">smart-card</a>
* indicates the respective authenticator can be contacted over ISO/IEC 7816 smart
* card with contacts.
*/
public static final AuthenticatorTransport SMART_CARD = new AuthenticatorTransport("smart-card");
/**
* <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-authenticatortransport-hybrid">hybrid</a>
* indicates the respective authenticator can be contacted using a combination of
* (often separate) data-transport and proximity mechanisms. This supports, for
* example, authentication on a desktop computer using a smartphone.
*/
public static final AuthenticatorTransport HYBRID = new AuthenticatorTransport("hybrid");
/**
* <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-authenticatortransport-internal">internal</a>
* indicates the respective authenticator is contacted using a client device-specific
* transport, i.e., it is a platform authenticator. These authenticators are not
* removable from the client device.
*/
public static final AuthenticatorTransport INTERNAL = new AuthenticatorTransport("internal");
private final String value;
AuthenticatorTransport(String value) {
this.value = value;
}
/**
* Get's the value.
* @return the value.
*/
public String getValue() {
return this.value;
}
/**
* Gets an instance of {@link AuthenticatorTransport}.
* @param value the value of the {@link AuthenticatorTransport}
* @return the {@link AuthenticatorTransport}
*/
public static AuthenticatorTransport valueOf(String value) {
switch (value) {
case "usb":
return USB;
case "nfc":
return NFC;
case "ble":
return BLE;
case "smart-card":
return SMART_CARD;
case "hybrid":
return HYBRID;
case "internal":
return INTERNAL;
default:
return new AuthenticatorTransport(value);
}
}
public static AuthenticatorTransport[] values() {
return new AuthenticatorTransport[] { USB, NFC, BLE, HYBRID, INTERNAL };
}
}
@@ -1,108 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
import java.io.Serial;
import java.io.Serializable;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;
import org.springframework.util.Assert;
/**
* An object representation of byte[].
*
* @author Rob Winch
* @since 6.4
*/
public final class Bytes implements Serializable {
@Serial
private static final long serialVersionUID = -3278138671365709777L;
private static final SecureRandom RANDOM = new SecureRandom();
private static final Base64.Encoder ENCODER = Base64.getUrlEncoder().withoutPadding();
private static final Base64.Decoder DECODER = Base64.getUrlDecoder();
private final byte[] bytes;
/**
* Creates a new instance
* @param bytes the raw base64UrlString that will be encoded.
*/
public Bytes(byte[] bytes) {
Assert.notNull(bytes, "bytes cannot be null");
this.bytes = bytes;
}
/**
* Gets the raw bytes.
* @return the bytes
*/
public byte[] getBytes() {
return Arrays.copyOf(this.bytes, this.bytes.length);
}
/**
* Gets the bytes as Base64 URL encoded String.
* @return
*/
public String toBase64UrlString() {
return ENCODER.encodeToString(getBytes());
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Bytes that) {
return that.toBase64UrlString().equals(toBase64UrlString());
}
return false;
}
@Override
public int hashCode() {
return toBase64UrlString().hashCode();
}
public String toString() {
return "Bytes[" + toBase64UrlString() + "]";
}
/**
* Creates a secure random {@link Bytes} with random bytes and sufficient entropy.
* @return a new secure random generated {@link Bytes}
*/
public static Bytes random() {
byte[] bytes = new byte[32];
RANDOM.nextBytes(bytes);
return new Bytes(bytes);
}
/**
* Creates a new instance from a base64 url string.
* @param base64UrlString the base64 url string
* @return the {@link Bytes}
*/
public static Bytes fromBase64(String base64UrlString) {
byte[] bytes = DECODER.decode(base64UrlString);
return new Bytes(bytes);
}
}
@@ -1,65 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
/**
* <a href=
* "https://www.w3.org/TR/webauthn-3/#sctn-alg-identifier">COSEAlgorithmIdentifier</a> is
* used to identify a cryptographic algorithm.
*
* @author Rob Winch
* @since 6.4
* @see PublicKeyCredentialParameters#getAlg()
*/
public final class COSEAlgorithmIdentifier {
public static final COSEAlgorithmIdentifier EdDSA = new COSEAlgorithmIdentifier(-8);
public static final COSEAlgorithmIdentifier ES256 = new COSEAlgorithmIdentifier(-7);
public static final COSEAlgorithmIdentifier ES384 = new COSEAlgorithmIdentifier(-35);
public static final COSEAlgorithmIdentifier ES512 = new COSEAlgorithmIdentifier(-36);
public static final COSEAlgorithmIdentifier RS256 = new COSEAlgorithmIdentifier(-257);
public static final COSEAlgorithmIdentifier RS384 = new COSEAlgorithmIdentifier(-258);
public static final COSEAlgorithmIdentifier RS512 = new COSEAlgorithmIdentifier(-259);
public static final COSEAlgorithmIdentifier RS1 = new COSEAlgorithmIdentifier(-65535);
private final long value;
private COSEAlgorithmIdentifier(long value) {
this.value = value;
}
public long getValue() {
return this.value;
}
@Override
public String toString() {
return String.valueOf(this.value);
}
public static COSEAlgorithmIdentifier[] values() {
return new COSEAlgorithmIdentifier[] { EdDSA, ES256, ES384, ES512, RS256, RS384, RS512, RS1 };
}
}
@@ -1,82 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
import java.io.Serial;
import java.io.Serializable;
/**
* Implements <a href=
* "https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-credProtect-extension">
* Credential Protection (credProtect)</a>.
*
* @author Rob Winch
* @since 6.4
*/
public class CredProtectAuthenticationExtensionsClientInput
implements AuthenticationExtensionsClientInput<CredProtectAuthenticationExtensionsClientInput.CredProtect> {
@Serial
private static final long serialVersionUID = -6418175591005843455L;
private final CredProtect input;
public CredProtectAuthenticationExtensionsClientInput(CredProtect input) {
this.input = input;
}
@Override
public String getExtensionId() {
return "credProtect";
}
@Override
public CredProtect getInput() {
return this.input;
}
public static class CredProtect implements Serializable {
@Serial
private static final long serialVersionUID = 109597301115842688L;
private final ProtectionPolicy credProtectionPolicy;
private final boolean enforceCredentialProtectionPolicy;
public CredProtect(ProtectionPolicy credProtectionPolicy, boolean enforceCredentialProtectionPolicy) {
this.enforceCredentialProtectionPolicy = enforceCredentialProtectionPolicy;
this.credProtectionPolicy = credProtectionPolicy;
}
public boolean isEnforceCredentialProtectionPolicy() {
return this.enforceCredentialProtectionPolicy;
}
public ProtectionPolicy getCredProtectionPolicy() {
return this.credProtectionPolicy;
}
public enum ProtectionPolicy {
USER_VERIFICATION_OPTIONAL, USER_VERIFICATION_OPTIONAL_WITH_CREDENTIAL_ID_LIST, USER_VERIFICATION_REQUIRED
}
}
}
@@ -1,92 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
import java.io.Serial;
import java.io.Serializable;
/**
* <a href=
* "https://www.w3.org/TR/webauthn-3/#dictdef-credentialpropertiesoutput">CredentialPropertiesOutput</a>
* is the Client extension output.
*
* @author Rob Winch
* @since 6.4
*/
public class CredentialPropertiesOutput
implements AuthenticationExtensionsClientOutput<CredentialPropertiesOutput.ExtensionOutput> {
@Serial
private static final long serialVersionUID = -3201699313968303331L;
/**
* The extension id.
*/
public static final String EXTENSION_ID = "credProps";
private final ExtensionOutput output;
/**
* Creates a new instance.
* @param rk is the resident key is discoverable
*/
public CredentialPropertiesOutput(boolean rk) {
this.output = new ExtensionOutput(rk);
}
@Override
public String getExtensionId() {
return EXTENSION_ID;
}
@Override
public ExtensionOutput getOutput() {
return this.output;
}
/**
* The output for {@link CredentialPropertiesOutput}
*
* @author Rob Winch
* @since 6.4
* @see #getOutput()
*/
public static final class ExtensionOutput implements Serializable {
@Serial
private static final long serialVersionUID = 4557406414847424019L;
private final boolean rk;
private ExtensionOutput(boolean rk) {
this.rk = rk;
}
/**
* This OPTIONAL property, known abstractly as the resident key credential
* property (i.e., client-side discoverable credential property), is a Boolean
* value indicating whether the PublicKeyCredential returned as a result of a
* registration ceremony is a client-side discoverable credential.
* @return is resident key credential property
*/
public boolean isRk() {
return this.rk;
}
}
}
@@ -1,136 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
import java.time.Instant;
import java.util.Set;
/**
* Represents a <a href="https://www.w3.org/TR/webauthn-3/#credential-record">Credential
* Record</a> that is stored by the Relying Party
* <a href="https://www.w3.org/TR/webauthn-3/#reg-ceremony-store-credential-record">after
* successful registration</a>.
*
* @author Rob Winch
* @since 6.4
*/
public interface CredentialRecord {
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#abstract-opdef-credential-record-type">credential.type</a>
* @return
*/
PublicKeyCredentialType getCredentialType();
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#abstract-opdef-credential-record-id">credential.id</a>.
* @return
*/
Bytes getCredentialId();
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#abstract-opdef-credential-record-publickey">publicKey</a>
* @return
*/
PublicKeyCose getPublicKey();
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#abstract-opdef-credential-record-signcount">authData.signCount</a>
* @return
*/
long getSignatureCount();
/**
* <a href=
* "https://www.w3.org/TR/webauthn-3/#abstract-opdef-credential-record-uvinitialized">uvInitialized</a>
* is the value of the UV (user verified) flag in authData and indicates whether any
* credential from this public key credential source has had the UV flag set.
* @return
*/
boolean isUvInitialized();
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#abstract-opdef-credential-record-transports">transpots</a>
* is the value returned from {@code response.getTransports()}.
* @return
*/
Set<AuthenticatorTransport> getTransports();
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#abstract-opdef-credential-record-backupeligible">backupElgible</a>
* flag is the same as the BE flag in authData.
* @return
*/
boolean isBackupEligible();
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#abstract-opdef-credential-record-backupstate">backupState</a>
* flag is the same as the BS flag in authData.
* @return
*/
boolean isBackupState();
/**
* A reference to the associated {@link PublicKeyCredentialUserEntity#getId()}
* @return
*/
Bytes getUserEntityUserId();
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#abstract-opdef-credential-record-attestationobject">attestationObject</a>
* is the value of the attestationObject attribute when the public key credential
* source was registered.
* @return the attestationObject
*/
Bytes getAttestationObject();
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#abstract-opdef-credential-record-attestationclientdatajson">attestationClientDataJSON</a>
* is the value of the attestationObject attribute when the public key credential
* source was registered.
* @return
*/
Bytes getAttestationClientDataJSON();
/**
* A human-readable label for this {@link CredentialRecord} assigned by the user.
* @return a label
*/
String getLabel();
/**
* The last time this {@link CredentialRecord} was used.
* @return the last time this {@link CredentialRecord} was used.
*/
Instant getLastUsed();
/**
* When this {@link CredentialRecord} was created.
* @return When this {@link CredentialRecord} was created.
*/
Instant getCreated();
}
@@ -1,64 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
import java.io.Serial;
/**
* An immutable {@link AuthenticationExtensionsClientInput}.
*
* @param <T> the input type
* @author Rob Winch
* @since 6.4
* @see AuthenticationExtensionsClientInputs
*/
public class ImmutableAuthenticationExtensionsClientInput<T> implements AuthenticationExtensionsClientInput<T> {
@Serial
private static final long serialVersionUID = -1738152485672656808L;
/**
* https://www.w3.org/TR/webauthn-3/#sctn-authenticator-credential-properties-extension
*/
public static final AuthenticationExtensionsClientInput<Boolean> credProps = new ImmutableAuthenticationExtensionsClientInput<>(
"credProps", true);
private final String extensionId;
private final T input;
/**
* Creates a new instance
* @param extensionId the extension id.
* @param input the input.
*/
public ImmutableAuthenticationExtensionsClientInput(String extensionId, T input) {
this.extensionId = extensionId;
this.input = input;
}
@Override
public String getExtensionId() {
return this.extensionId;
}
@Override
public T getInput() {
return this.input;
}
}
@@ -1,49 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
import java.io.Serial;
import java.util.Arrays;
import java.util.List;
/**
* An immutable implementation of {@link AuthenticationExtensionsClientInputs}.
*
* @author Rob Winch
* @since 6.4
*/
public class ImmutableAuthenticationExtensionsClientInputs implements AuthenticationExtensionsClientInputs {
@Serial
private static final long serialVersionUID = 4277817521578485720L;
private final List<AuthenticationExtensionsClientInput> inputs;
public ImmutableAuthenticationExtensionsClientInputs(List<AuthenticationExtensionsClientInput> inputs) {
this.inputs = inputs;
}
public ImmutableAuthenticationExtensionsClientInputs(AuthenticationExtensionsClientInput... inputs) {
this(Arrays.asList(inputs));
}
@Override
public List<AuthenticationExtensionsClientInput> getInputs() {
return this.inputs;
}
}
@@ -1,48 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
import java.io.Serial;
import java.util.Arrays;
import java.util.List;
/**
* An immutable implementation of {@link AuthenticationExtensionsClientOutputs}.
*
* @author Rob Winch
*/
public class ImmutableAuthenticationExtensionsClientOutputs implements AuthenticationExtensionsClientOutputs {
@Serial
private static final long serialVersionUID = -4656390173585180393L;
private final List<AuthenticationExtensionsClientOutput<?>> outputs;
public ImmutableAuthenticationExtensionsClientOutputs(List<AuthenticationExtensionsClientOutput<?>> outputs) {
this.outputs = outputs;
}
public ImmutableAuthenticationExtensionsClientOutputs(AuthenticationExtensionsClientOutput<?>... outputs) {
this(Arrays.asList(outputs));
}
@Override
public List<AuthenticationExtensionsClientOutput<?>> getOutputs() {
return this.outputs;
}
}
@@ -1,285 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
import java.time.Instant;
import java.util.Set;
/**
* An immutable {@link CredentialRecord}.
*
* @author Rob Winch
* @since 6.4
*/
public final class ImmutableCredentialRecord implements CredentialRecord {
private final PublicKeyCredentialType credentialType;
private final Bytes credentialId;
private final Bytes userEntityUserId;
private final PublicKeyCose publicKey;
private final long signatureCount;
private final boolean uvInitialized;
private final Set<AuthenticatorTransport> transports;
private final boolean backupEligible;
private final boolean backupState;
private final Bytes attestationObject;
private final Bytes attestationClientDataJSON;
private final Instant created;
private final Instant lastUsed;
private final String label;
private ImmutableCredentialRecord(PublicKeyCredentialType credentialType, Bytes credentialId,
Bytes userEntityUserId, PublicKeyCose publicKey, long signatureCount, boolean uvInitialized,
Set<AuthenticatorTransport> transports, boolean backupEligible, boolean backupState,
Bytes attestationObject, Bytes attestationClientDataJSON, Instant created, Instant lastUsed, String label) {
this.credentialType = credentialType;
this.credentialId = credentialId;
this.userEntityUserId = userEntityUserId;
this.publicKey = publicKey;
this.signatureCount = signatureCount;
this.uvInitialized = uvInitialized;
this.transports = transports;
this.backupEligible = backupEligible;
this.backupState = backupState;
this.attestationObject = attestationObject;
this.attestationClientDataJSON = attestationClientDataJSON;
this.created = created;
this.lastUsed = lastUsed;
this.label = label;
}
@Override
public PublicKeyCredentialType getCredentialType() {
return this.credentialType;
}
@Override
public Bytes getCredentialId() {
return this.credentialId;
}
@Override
public Bytes getUserEntityUserId() {
return this.userEntityUserId;
}
@Override
public PublicKeyCose getPublicKey() {
return this.publicKey;
}
@Override
public long getSignatureCount() {
return this.signatureCount;
}
@Override
public boolean isUvInitialized() {
return this.uvInitialized;
}
@Override
public Set<AuthenticatorTransport> getTransports() {
return this.transports;
}
@Override
public boolean isBackupEligible() {
return this.backupEligible;
}
@Override
public boolean isBackupState() {
return this.backupState;
}
@Override
public Bytes getAttestationObject() {
return this.attestationObject;
}
@Override
public Bytes getAttestationClientDataJSON() {
return this.attestationClientDataJSON;
}
@Override
public Instant getCreated() {
return this.created;
}
@Override
public Instant getLastUsed() {
return this.lastUsed;
}
@Override
public String getLabel() {
return this.label;
}
public static ImmutableCredentialRecordBuilder builder() {
return new ImmutableCredentialRecordBuilder();
}
public static ImmutableCredentialRecordBuilder fromCredentialRecord(CredentialRecord credentialRecord) {
return new ImmutableCredentialRecordBuilder(credentialRecord);
}
public static final class ImmutableCredentialRecordBuilder {
private PublicKeyCredentialType credentialType;
private Bytes credentialId;
private Bytes userEntityUserId;
private PublicKeyCose publicKey;
private long signatureCount;
private boolean uvInitialized;
private Set<AuthenticatorTransport> transports;
private boolean backupEligible;
private boolean backupState;
private Bytes attestationObject;
private Bytes attestationClientDataJSON;
private Instant created = Instant.now();
private Instant lastUsed = this.created;
private String label;
private ImmutableCredentialRecordBuilder() {
}
private ImmutableCredentialRecordBuilder(CredentialRecord other) {
this.credentialType = other.getCredentialType();
this.credentialId = other.getCredentialId();
this.userEntityUserId = other.getUserEntityUserId();
this.publicKey = other.getPublicKey();
this.signatureCount = other.getSignatureCount();
this.uvInitialized = other.isUvInitialized();
this.transports = other.getTransports();
this.backupEligible = other.isBackupEligible();
this.backupState = other.isBackupState();
this.attestationObject = other.getAttestationObject();
this.attestationClientDataJSON = other.getAttestationClientDataJSON();
this.created = other.getCreated();
this.lastUsed = other.getLastUsed();
this.label = other.getLabel();
}
public ImmutableCredentialRecordBuilder credentialType(PublicKeyCredentialType credentialType) {
this.credentialType = credentialType;
return this;
}
public ImmutableCredentialRecordBuilder credentialId(Bytes credentialId) {
this.credentialId = credentialId;
return this;
}
public ImmutableCredentialRecordBuilder userEntityUserId(Bytes userEntityUserId) {
this.userEntityUserId = userEntityUserId;
return this;
}
public ImmutableCredentialRecordBuilder publicKey(PublicKeyCose publicKey) {
this.publicKey = publicKey;
return this;
}
public ImmutableCredentialRecordBuilder signatureCount(long signatureCount) {
this.signatureCount = signatureCount;
return this;
}
public ImmutableCredentialRecordBuilder uvInitialized(boolean uvInitialized) {
this.uvInitialized = uvInitialized;
return this;
}
public ImmutableCredentialRecordBuilder transports(Set<AuthenticatorTransport> transports) {
this.transports = transports;
return this;
}
public ImmutableCredentialRecordBuilder backupEligible(boolean backupEligible) {
this.backupEligible = backupEligible;
return this;
}
public ImmutableCredentialRecordBuilder backupState(boolean backupState) {
this.backupState = backupState;
return this;
}
public ImmutableCredentialRecordBuilder attestationObject(Bytes attestationObject) {
this.attestationObject = attestationObject;
return this;
}
public ImmutableCredentialRecordBuilder attestationClientDataJSON(Bytes attestationClientDataJSON) {
this.attestationClientDataJSON = attestationClientDataJSON;
return this;
}
public ImmutableCredentialRecordBuilder created(Instant created) {
this.created = created;
return this;
}
public ImmutableCredentialRecordBuilder lastUsed(Instant lastUsed) {
this.lastUsed = lastUsed;
return this;
}
public ImmutableCredentialRecordBuilder label(String label) {
this.label = label;
return this;
}
public ImmutableCredentialRecord build() {
return new ImmutableCredentialRecord(this.credentialType, this.credentialId, this.userEntityUserId,
this.publicKey, this.signatureCount, this.uvInitialized, this.transports, this.backupEligible,
this.backupState, this.attestationObject, this.attestationClientDataJSON, this.created,
this.lastUsed, this.label);
}
}
}
@@ -1,55 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
import java.util.Arrays;
import java.util.Base64;
/**
* An immutable {@link PublicKeyCose}
*
* @author Rob Winch
* @since 6.4
*/
public class ImmutablePublicKeyCose implements PublicKeyCose {
private final byte[] bytes;
/**
* Creates a new instance.
* @param bytes the raw bytes of the public key.
*/
public ImmutablePublicKeyCose(byte[] bytes) {
this.bytes = Arrays.copyOf(bytes, bytes.length);
}
@Override
public byte[] getBytes() {
return Arrays.copyOf(this.bytes, this.bytes.length);
}
/**
* Creates a new instance form a Base64 URL encoded String
* @param base64EncodedString the base64EncodedString encoded String
* @return
*/
public static ImmutablePublicKeyCose fromBase64(String base64EncodedString) {
byte[] decode = Base64.getUrlDecoder().decode(base64EncodedString);
return new ImmutablePublicKeyCose(decode);
}
}
@@ -1,193 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
import java.io.Serial;
/**
* <a href=
* "https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialuserentity">PublicKeyCredentialUserEntity</a>
* is used to supply additional
* <a href="https://www.w3.org/TR/webauthn-3/#user-account">user account</a> attributes
* when creating a new credential.
*
* @author Rob Winch
* @since 6.4
*/
public final class ImmutablePublicKeyCredentialUserEntity implements PublicKeyCredentialUserEntity {
@Serial
private static final long serialVersionUID = -3438693960347279759L;
/**
* When inherited by PublicKeyCredentialUserEntity, it is a human-palatable identifier
* for a user account. It is intended only for display, i.e., aiding the user in
* determining the difference between user accounts with similar displayNames. For
* example, "alexm", "alex.mueller@example.com" or "+14255551234".
*
* The Relying Party MAY let the user choose this value. The Relying Party SHOULD
* perform enforcement, as prescribed in Section 3.4.3 of [RFC8265] for the
* UsernameCasePreserved Profile of the PRECIS IdentifierClass [RFC8264], when setting
* name's value, or displaying the value to the user.
*
* This string MAY contain language and direction metadata. Relying Parties SHOULD
* consider providing this information. See 6.4.2 Language and Direction Encoding
* about how this metadata is encoded.
*
* Clients SHOULD perform enforcement, as prescribed in Section 3.4.3 of [RFC8265] for
* the UsernameCasePreserved Profile of the PRECIS IdentifierClass [RFC8264], on
* name's value prior to displaying the value to the user or including the value as a
* parameter of the authenticatorMakeCredential operation.
*/
private final String name;
/**
* The user handle of the user account entity. A user handle is an opaque byte
* sequence with a maximum size of 64 bytes, and is not meant to be displayed to the
* user.
*
* To ensure secure operation, authentication and authorization decisions MUST be made
* on the basis of this id member, not the displayName nor name members. See Section
* 6.1 of [RFC8266].
*
* The user handle MUST NOT contain personally identifying information about the user,
* such as a username or e-mail address; see 14.6.1 User Handle Contents for details.
* The user handle MUST NOT be empty, though it MAY be null.
*
* Note: the user handle ought not be a constant value across different accounts, even
* for non-discoverable credentials, because some authenticators always create
* discoverable credentials. Thus a constant user handle would prevent a user from
* using such an authenticator with more than one account at the Relying Party.
*/
private final Bytes id;
/**
* A human-palatable name for the user account, intended only for display. The Relying
* Party SHOULD let the user choose this, and SHOULD NOT restrict the choice more than
* necessary.
*
* Relying Parties SHOULD perform enforcement, as prescribed in Section 2.3 of
* [RFC8266] for the Nickname Profile of the PRECIS FreeformClass [RFC8264], when
* setting displayName's value, or displaying the value to the user.
*
* This string MAY contain language and direction metadata. Relying Parties SHOULD
* consider providing this information. See 6.4.2 Language and Direction Encoding
* about how this metadata is encoded.
*
* Clients SHOULD perform enforcement, as prescribed in Section 2.3 of [RFC8266] for
* the Nickname Profile of the PRECIS FreeformClass [RFC8264], on displayName's value
* prior to displaying the value to the user or including the value as a parameter of
* the authenticatorMakeCredential operation.
*
* When clients, client platforms, or authenticators display a displayName's value,
* they should always use UI elements to provide a clear boundary around the displayed
* value, and not allow overflow into other elements [css-overflow-3].
*
* Authenticators MUST accept and store a 64-byte minimum length for a displayName
* member's value. Authenticators MAY truncate a displayName member's value so that it
* fits within 64 bytes. See 6.4.1 String Truncation about truncation and other
* considerations.
*/
private final String displayName;
private ImmutablePublicKeyCredentialUserEntity(String name, Bytes id, String displayName) {
this.name = name;
this.id = id;
this.displayName = displayName;
}
@Override
public String getName() {
return this.name;
}
@Override
public Bytes getId() {
return this.id;
}
@Override
public String getDisplayName() {
return this.displayName;
}
/**
* Create a new {@link PublicKeyCredentialUserEntityBuilder}
* @return a new {@link PublicKeyCredentialUserEntityBuilder}
*/
public static PublicKeyCredentialUserEntityBuilder builder() {
return new PublicKeyCredentialUserEntityBuilder();
}
/**
* Used to build {@link PublicKeyCredentialUserEntity}.
*
* @author Rob Winch
* @since 6.4
*/
public static final class PublicKeyCredentialUserEntityBuilder {
private String name;
private Bytes id;
private String displayName;
private PublicKeyCredentialUserEntityBuilder() {
}
/**
* Sets the {@link #getName()} property.
* @param name the name
* @return the {@link PublicKeyCredentialUserEntityBuilder}
*/
public PublicKeyCredentialUserEntityBuilder name(String name) {
this.name = name;
return this;
}
/**
* Sets the {@link #getId()} property.
* @param id the id
* @return the {@link PublicKeyCredentialUserEntityBuilder}
*/
public PublicKeyCredentialUserEntityBuilder id(Bytes id) {
this.id = id;
return this;
}
/**
* Sets the {@link #getDisplayName()} property.
* @param displayName the display name
* @return the {@link PublicKeyCredentialUserEntityBuilder}
*/
public PublicKeyCredentialUserEntityBuilder displayName(String displayName) {
this.displayName = displayName;
return this;
}
/**
* Builds a new {@link PublicKeyCredentialUserEntity}
* @return a new {@link PublicKeyCredentialUserEntity}
*/
public PublicKeyCredentialUserEntity build() {
return new ImmutablePublicKeyCredentialUserEntity(this.name, this.id, this.displayName);
}
}
}
@@ -1,34 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
/**
* A <a href="https://www.w3.org/TR/webauthn-3/#sctn-encoded-credPubKey-examples">COSE
* encoded public key</a>.
*
* @author Rob Winch
* @since 6.4
*/
public interface PublicKeyCose {
/**
* The byes of a COSE encoded public key.
* @return the bytes of a COSE encoded public key.
*/
byte[] getBytes();
}
@@ -1,229 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
import java.io.Serial;
import java.io.Serializable;
/**
* <a href="https://www.w3.org/TR/webauthn-3/#iface-pkcredential">PublicKeyCredential</a>
* contains the attributes that are returned to the caller when a new credential is
* created, or a new assertion is requested.
*
* @author Rob Winch
* @since 6.4
*/
public final class PublicKeyCredential<R extends AuthenticatorResponse> implements Serializable {
@Serial
private static final long serialVersionUID = -1864035469276082606L;
private final String id;
private final PublicKeyCredentialType type;
private final Bytes rawId;
private final R response;
private final AuthenticatorAttachment authenticatorAttachment;
private final AuthenticationExtensionsClientOutputs clientExtensionResults;
private PublicKeyCredential(String id, PublicKeyCredentialType type, Bytes rawId, R response,
AuthenticatorAttachment authenticatorAttachment,
AuthenticationExtensionsClientOutputs clientExtensionResults) {
this.id = id;
this.type = type;
this.rawId = rawId;
this.response = response;
this.authenticatorAttachment = authenticatorAttachment;
this.clientExtensionResults = clientExtensionResults;
}
/**
* The
* <a href="https://www.w3.org/TR/credential-management-1/#dom-credential-id">id</a>
* attribute is inherited from Credential, though PublicKeyCredential overrides
* Credential's getter, instead returning the base64url encoding of the data contained
* in the object's [[identifier]] internal slot.
*/
public String getId() {
return this.id;
}
/**
* The <a href=
* "https://www.w3.org/TR/credential-management-1/#dom-credential-type">type</a>
* attribute returns the value of the object's interface object's [[type]] slot, which
* specifies the credential type represented by this object.
* @return the credential type
*/
public PublicKeyCredentialType getType() {
return this.type;
}
/**
* The
* <a href="https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-rawid">rawId</a>
* returns the raw identifier.
* @return the raw id
*/
public Bytes getRawId() {
return this.rawId;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-response">response</a>
* to the client's request to either create a public key credential, or generate an
* authentication assertion.
* @return the response
*/
public R getResponse() {
return this.response;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-authenticatorattachment">authenticatorAttachment</a>
* reports the <a href=
* "https://www.w3.org/TR/webauthn-3/#authenticator-attachment-modality">authenticator
* attachment modality</a> in effect at the time the navigator.credentials.create() or
* navigator.credentials.get() methods successfully complete.
* @return the authenticator attachment
*/
public AuthenticatorAttachment getAuthenticatorAttachment() {
return this.authenticatorAttachment;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-getclientextensionresults">clientExtensionsResults</a>
* is a mapping of extension identifier to client extension output.
* @return the extension results
*/
public AuthenticationExtensionsClientOutputs getClientExtensionResults() {
return this.clientExtensionResults;
}
/**
* Creates a new {@link PublicKeyCredentialBuilder}
* @param <T> the response type
* @return the {@link PublicKeyCredentialBuilder}
*/
public static <T extends AuthenticatorResponse> PublicKeyCredentialBuilder<T> builder() {
return new PublicKeyCredentialBuilder<>();
}
/**
* The {@link PublicKeyCredentialBuilder}
*
* @param <R> the response type
* @author Rob Winch
* @since 6.4
*/
public static final class PublicKeyCredentialBuilder<R extends AuthenticatorResponse> {
private String id;
private PublicKeyCredentialType type;
private Bytes rawId;
private R response;
private AuthenticatorAttachment authenticatorAttachment;
private AuthenticationExtensionsClientOutputs clientExtensionResults;
private PublicKeyCredentialBuilder() {
}
/**
* Sets the {@link #getId()} property
* @param id the id
* @return the PublicKeyCredentialBuilder
*/
public PublicKeyCredentialBuilder id(String id) {
this.id = id;
return this;
}
/**
* Sets the {@link #getType()} property.
* @param type the type
* @return the PublicKeyCredentialBuilder
*/
public PublicKeyCredentialBuilder type(PublicKeyCredentialType type) {
this.type = type;
return this;
}
/**
* Sets the {@link #getRawId()} property.
* @param rawId the raw id
* @return the PublicKeyCredentialBuilder
*/
public PublicKeyCredentialBuilder rawId(Bytes rawId) {
this.rawId = rawId;
return this;
}
/**
* Sets the {@link #getResponse()} property.
* @param response the response
* @return the PublicKeyCredentialBuilder
*/
public PublicKeyCredentialBuilder response(R response) {
this.response = response;
return this;
}
/**
* Sets the {@link #getAuthenticatorAttachment()} property.
* @param authenticatorAttachment the authenticator attachement
* @return the PublicKeyCredentialBuilder
*/
public PublicKeyCredentialBuilder authenticatorAttachment(AuthenticatorAttachment authenticatorAttachment) {
this.authenticatorAttachment = authenticatorAttachment;
return this;
}
/**
* Sets the {@link #getClientExtensionResults()} property.
* @param clientExtensionResults the client extension results
* @return the PublicKeyCredentialBuilder
*/
public PublicKeyCredentialBuilder clientExtensionResults(
AuthenticationExtensionsClientOutputs clientExtensionResults) {
this.clientExtensionResults = clientExtensionResults;
return this;
}
/**
* Creates a new {@link PublicKeyCredential}
* @return a new {@link PublicKeyCredential}
*/
public PublicKeyCredential<R> build() {
return new PublicKeyCredential(this.id, this.type, this.rawId, this.response, this.authenticatorAttachment,
this.clientExtensionResults);
}
}
}
@@ -1,332 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
/**
* Represents the <a href=
* "https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialcreationoptions">PublicKeyCredentialCreationOptions</a>
* which is an argument to <a href=
* "https://w3c.github.io/webappsec-credential-management/#dom-credentialscontainer-create">creating</a>
* a new credential.
*
* @author Rob Winch
* @since 6.4
*/
public final class PublicKeyCredentialCreationOptions {
private final PublicKeyCredentialRpEntity rp;
private final PublicKeyCredentialUserEntity user;
private final Bytes challenge;
private final List<PublicKeyCredentialParameters> pubKeyCredParams;
private final Duration timeout;
private final List<PublicKeyCredentialDescriptor> excludeCredentials;
private final AuthenticatorSelectionCriteria authenticatorSelection;
private final AttestationConveyancePreference attestation;
private final AuthenticationExtensionsClientInputs extensions;
private PublicKeyCredentialCreationOptions(PublicKeyCredentialRpEntity rp, PublicKeyCredentialUserEntity user,
Bytes challenge, List<PublicKeyCredentialParameters> pubKeyCredParams, Duration timeout,
List<PublicKeyCredentialDescriptor> excludeCredentials,
AuthenticatorSelectionCriteria authenticatorSelection, AttestationConveyancePreference attestation,
AuthenticationExtensionsClientInputs extensions) {
this.rp = rp;
this.user = user;
this.challenge = challenge;
this.pubKeyCredParams = pubKeyCredParams;
this.timeout = timeout;
this.excludeCredentials = excludeCredentials;
this.authenticatorSelection = authenticatorSelection;
this.attestation = attestation;
this.extensions = extensions;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-rp">rp</a>
* property contains data about the Relying Party responsible for the request.
* @return the relying party
*/
public PublicKeyCredentialRpEntity getRp() {
return this.rp;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-user">user</a>
* contains names and an identifier for the user account performing the registration.
* @return the user
*/
public PublicKeyCredentialUserEntity getUser() {
return this.user;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-challenge">challenge</a>
* specifies the challenge that the authenticator signs, along with other data, when
* producing an attestation object for the newly created credential.
* @return the challenge
*/
public Bytes getChallenge() {
return this.challenge;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-pubkeycredparams">publicKeyCredParams</a>
* params lisst the key types and signature algorithms the Relying Party Supports,
* ordered from most preferred to least preferred.
* @return the public key credential parameters
*/
public List<PublicKeyCredentialParameters> getPubKeyCredParams() {
return this.pubKeyCredParams;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-timeout">timeout</a>
* property specifies a time, in milliseconds, that the Relying Party is willing to
* wait for the call to complete.
* @return the timeout
*/
public Duration getTimeout() {
return this.timeout;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-excludecredentials">excludeCredentials</a>
* property is the OPTIONAL member used by the Relying Party to list any existing
* credentials mapped to this user account (as identified by user.id).
* @return exclude credentials
*/
public List<PublicKeyCredentialDescriptor> getExcludeCredentials() {
return this.excludeCredentials;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-authenticatorselection">authenticatorSelection</a>
* property is an OPTIONAL member used by the Relying Party to list any existing
* credentials mapped to this user account (as identified by user.id).
* @return the authenticatorSelection
*/
public AuthenticatorSelectionCriteria getAuthenticatorSelection() {
return this.authenticatorSelection;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-attestation">attestation</a>
* property is an OPTIONAL member used by the Relying Party to specify a preference
* regarding attestation conveyance.
* @return the attestation preference
*/
public AttestationConveyancePreference getAttestation() {
return this.attestation;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-extensions">extensions</a>
* property is an OPTIONAL member used by the Relying Party to provide client
* extension inputs requesting additional processing by the client and authenticator.
* @return the extensions
*/
public AuthenticationExtensionsClientInputs getExtensions() {
return this.extensions;
}
/**
* Creates a new {@link PublicKeyCredentialCreationOptions}
* @return a new {@link PublicKeyCredentialCreationOptions}
*/
public static PublicKeyCredentialCreationOptionsBuilder builder() {
return new PublicKeyCredentialCreationOptionsBuilder();
}
/**
* Used to build {@link PublicKeyCredentialCreationOptions}.
*
* @author Rob Winch
* @since 6.4
*/
public static final class PublicKeyCredentialCreationOptionsBuilder {
private PublicKeyCredentialRpEntity rp;
private PublicKeyCredentialUserEntity user;
private Bytes challenge;
private List<PublicKeyCredentialParameters> pubKeyCredParams = new ArrayList<>();
private Duration timeout;
private List<PublicKeyCredentialDescriptor> excludeCredentials = new ArrayList<>();
private AuthenticatorSelectionCriteria authenticatorSelection;
private AttestationConveyancePreference attestation;
private AuthenticationExtensionsClientInputs extensions;
private PublicKeyCredentialCreationOptionsBuilder() {
}
/**
* Sets the {@link #getRp()} property.
* @param rp the relying party
* @return the PublicKeyCredentialCreationOptionsBuilder
*/
public PublicKeyCredentialCreationOptionsBuilder rp(PublicKeyCredentialRpEntity rp) {
this.rp = rp;
return this;
}
/**
* Sets the {@link #getUser()} property.
* @param user the user entity
* @return the PublicKeyCredentialCreationOptionsBuilder
*/
public PublicKeyCredentialCreationOptionsBuilder user(PublicKeyCredentialUserEntity user) {
this.user = user;
return this;
}
/**
* Sets the {@link #getChallenge()} property.
* @param challenge the challenge
* @return the PublicKeyCredentialCreationOptionsBuilder
*/
public PublicKeyCredentialCreationOptionsBuilder challenge(Bytes challenge) {
this.challenge = challenge;
return this;
}
/**
* Sets the {@link #getPubKeyCredParams()} property.
* @param pubKeyCredParams the public key credential parameters
* @return the PublicKeyCredentialCreationOptionsBuilder
*/
public PublicKeyCredentialCreationOptionsBuilder pubKeyCredParams(
PublicKeyCredentialParameters... pubKeyCredParams) {
return pubKeyCredParams(Arrays.asList(pubKeyCredParams));
}
/**
* Sets the {@link #getPubKeyCredParams()} property.
* @param pubKeyCredParams the public key credential parameters
* @return the PublicKeyCredentialCreationOptionsBuilder
*/
public PublicKeyCredentialCreationOptionsBuilder pubKeyCredParams(
List<PublicKeyCredentialParameters> pubKeyCredParams) {
this.pubKeyCredParams = pubKeyCredParams;
return this;
}
/**
* Sets the {@link #getTimeout()} property.
* @param timeout the timeout
* @return the PublicKeyCredentialCreationOptionsBuilder
*/
public PublicKeyCredentialCreationOptionsBuilder timeout(Duration timeout) {
this.timeout = timeout;
return this;
}
/**
* Sets the {@link #getExcludeCredentials()} property.
* @param excludeCredentials the excluded credentials.
* @return the PublicKeyCredentialCreationOptionsBuilder
*/
public PublicKeyCredentialCreationOptionsBuilder excludeCredentials(
List<PublicKeyCredentialDescriptor> excludeCredentials) {
this.excludeCredentials = excludeCredentials;
return this;
}
/**
* Sets the {@link #getAuthenticatorSelection()} property.
* @param authenticatorSelection the authenticator selection
* @return the PublicKeyCredentialCreationOptionsBuilder
*/
public PublicKeyCredentialCreationOptionsBuilder authenticatorSelection(
AuthenticatorSelectionCriteria authenticatorSelection) {
this.authenticatorSelection = authenticatorSelection;
return this;
}
/**
* Sets the {@link #getAttestation()} property.
* @param attestation the attestation
* @return the PublicKeyCredentialCreationOptionsBuilder
*/
public PublicKeyCredentialCreationOptionsBuilder attestation(AttestationConveyancePreference attestation) {
this.attestation = attestation;
return this;
}
/**
* Sets the {@link #getExtensions()} property.
* @param extensions the extensions
* @return the PublicKeyCredentialCreationOptionsBuilder
*/
public PublicKeyCredentialCreationOptionsBuilder extensions(AuthenticationExtensionsClientInputs extensions) {
this.extensions = extensions;
return this;
}
/**
* Allows customizing the builder using the {@link Consumer} that is passed in.
* @param customizer the {@link Consumer} that can be used to customize the
* {@link PublicKeyCredentialCreationOptionsBuilder}
* @return the PublicKeyCredentialCreationOptionsBuilder
*/
public PublicKeyCredentialCreationOptionsBuilder customize(
Consumer<PublicKeyCredentialCreationOptionsBuilder> customizer) {
customizer.accept(this);
return this;
}
/**
* Builds a new {@link PublicKeyCredentialCreationOptions}
* @return the new {@link PublicKeyCredentialCreationOptions}
*/
public PublicKeyCredentialCreationOptions build() {
return new PublicKeyCredentialCreationOptions(this.rp, this.user, this.challenge, this.pubKeyCredParams,
this.timeout, this.excludeCredentials, this.authenticatorSelection, this.attestation,
this.extensions);
}
}
}
@@ -1,159 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
import java.io.Serial;
import java.io.Serializable;
import java.util.Set;
/**
* <a href=
* "https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialdescriptor">PublicKeyCredentialDescriptor</a>
* identifies a specific public key credential. It is used in create() to prevent creating
* duplicate credentials on the same authenticator, and in get() to determine if and how
* the credential can currently be reached by the client. It mirrors some fields of the
* PublicKeyCredential object returned by create() and get().
*
* @author Rob Winch
* @since 6.4
*/
public final class PublicKeyCredentialDescriptor implements Serializable {
@Serial
private static final long serialVersionUID = 8793385059692676240L;
private final PublicKeyCredentialType type;
private final Bytes id;
private final Set<AuthenticatorTransport> transports;
private PublicKeyCredentialDescriptor(PublicKeyCredentialType type, Bytes id,
Set<AuthenticatorTransport> transports) {
this.type = type;
this.id = id;
this.transports = transports;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialdescriptor-type">type</a>
* property contains the type of the public key credential the caller is referring to.
* @return the type
*/
public PublicKeyCredentialType getType() {
return this.type;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialdescriptor-id">id</a>
* property contains the credential ID of the public key credential the caller is
* referring to.
* @return the id
*/
public Bytes getId() {
return this.id;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialdescriptor-transports">transports</a>
* property is an OPTIONAL member that contains a hint as to how the client might
* communicate with the managing authenticator of the public key credential the caller
* is referring to.
* @return the transports
*/
public Set<AuthenticatorTransport> getTransports() {
return this.transports;
}
/**
* Creates a new {@link PublicKeyCredentialDescriptorBuilder}
* @return a new {@link PublicKeyCredentialDescriptorBuilder}
*/
public static PublicKeyCredentialDescriptorBuilder builder() {
return new PublicKeyCredentialDescriptorBuilder();
}
/**
* Used to create {@link PublicKeyCredentialDescriptor}
*
* @author Rob Winch
* @since 6.4
*/
public static final class PublicKeyCredentialDescriptorBuilder {
private PublicKeyCredentialType type = PublicKeyCredentialType.PUBLIC_KEY;
private Bytes id;
private Set<AuthenticatorTransport> transports;
private PublicKeyCredentialDescriptorBuilder() {
}
/**
* Sets the {@link #getType()} property.
* @param type the type
* @return the {@link PublicKeyCredentialDescriptorBuilder}
*/
public PublicKeyCredentialDescriptorBuilder type(PublicKeyCredentialType type) {
this.type = type;
return this;
}
/**
* Sets the {@link #getId()} property.
* @param id the id
* @return the {@link PublicKeyCredentialDescriptorBuilder}
*/
public PublicKeyCredentialDescriptorBuilder id(Bytes id) {
this.id = id;
return this;
}
/**
* Sets the {@link #getTransports()} property.
* @param transports the transports
* @return the {@link PublicKeyCredentialDescriptorBuilder}
*/
public PublicKeyCredentialDescriptorBuilder transports(Set<AuthenticatorTransport> transports) {
this.transports = transports;
return this;
}
/**
* Sets the {@link #getTransports()} property.
* @param transports the transports
* @return the {@link PublicKeyCredentialDescriptorBuilder}
*/
public PublicKeyCredentialDescriptorBuilder transports(AuthenticatorTransport... transports) {
return transports(Set.of(transports));
}
/**
* Create a new {@link PublicKeyCredentialDescriptor}
* @return a new {@link PublicKeyCredentialDescriptor}
*/
public PublicKeyCredentialDescriptor build() {
return new PublicKeyCredentialDescriptor(this.type, this.id, this.transports);
}
}
}
@@ -1,99 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialparameters">PublicKeyCredentialParameters</a>
* is used to supply additional parameters when creating a new credential.
*
* @author Rob Winch
* @since 6.4
* @see PublicKeyCredentialCreationOptions#getPubKeyCredParams()
*/
public final class PublicKeyCredentialParameters {
public static final PublicKeyCredentialParameters EdDSA = new PublicKeyCredentialParameters(
COSEAlgorithmIdentifier.EdDSA);
public static final PublicKeyCredentialParameters ES256 = new PublicKeyCredentialParameters(
COSEAlgorithmIdentifier.ES256);
public static final PublicKeyCredentialParameters ES384 = new PublicKeyCredentialParameters(
COSEAlgorithmIdentifier.ES384);
public static final PublicKeyCredentialParameters ES512 = new PublicKeyCredentialParameters(
COSEAlgorithmIdentifier.ES512);
public static final PublicKeyCredentialParameters RS256 = new PublicKeyCredentialParameters(
COSEAlgorithmIdentifier.RS256);
public static final PublicKeyCredentialParameters RS384 = new PublicKeyCredentialParameters(
COSEAlgorithmIdentifier.RS384);
public static final PublicKeyCredentialParameters RS512 = new PublicKeyCredentialParameters(
COSEAlgorithmIdentifier.RS512);
public static final PublicKeyCredentialParameters RS1 = new PublicKeyCredentialParameters(
COSEAlgorithmIdentifier.RS1);
/**
* This member specifies the type of credential to be created. The value SHOULD be a
* member of PublicKeyCredentialType but client platforms MUST ignore unknown values,
* ignoring any PublicKeyCredentialParameters with an unknown type.
*/
private final PublicKeyCredentialType type;
/**
* This member specifies the cryptographic signature algorithm with which the newly
* generated credential will be used, and thus also the type of asymmetric key pair to
* be generated, e.g., RSA or Elliptic Curve.
*/
private final COSEAlgorithmIdentifier alg;
private PublicKeyCredentialParameters(COSEAlgorithmIdentifier alg) {
this(PublicKeyCredentialType.PUBLIC_KEY, alg);
}
private PublicKeyCredentialParameters(PublicKeyCredentialType type, COSEAlgorithmIdentifier alg) {
this.type = type;
this.alg = alg;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialparameters-type">type</a>
* property member specifies the type of credential to be created.
* @return the type
*/
public PublicKeyCredentialType getType() {
return this.type;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialparameters-alg">alg</a>
* member specifies the cryptographic signature algorithm with which the newly
* generated credential will be used, and thus also the type of asymmetric key pair to
* be generated, e.g., RSA or Elliptic Curve.
* @return the algorithm
*/
public COSEAlgorithmIdentifier getAlg() {
return this.alg;
}
}
@@ -1,253 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.Collections;
import java.util.List;
import java.util.function.Consumer;
import org.springframework.util.Assert;
/**
* <a href=
* "https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialrequestoptions">PublicKeyCredentialRequestOptions</a>
* contains the information to create an assertion used for authentication.
*
* @author Rob Winch
* @since 6.4
*/
public final class PublicKeyCredentialRequestOptions implements Serializable {
@Serial
private static final long serialVersionUID = -2970057592835694354L;
private final Bytes challenge;
private final Duration timeout;
private final String rpId;
private final List<PublicKeyCredentialDescriptor> allowCredentials;
private final UserVerificationRequirement userVerification;
private final AuthenticationExtensionsClientInputs extensions;
private PublicKeyCredentialRequestOptions(Bytes challenge, Duration timeout, String rpId,
List<PublicKeyCredentialDescriptor> allowCredentials, UserVerificationRequirement userVerification,
AuthenticationExtensionsClientInputs extensions) {
Assert.notNull(challenge, "challenge cannot be null");
Assert.hasText(rpId, "rpId cannot be empty");
this.challenge = challenge;
this.timeout = timeout;
this.rpId = rpId;
this.allowCredentials = allowCredentials;
this.userVerification = userVerification;
this.extensions = extensions;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-challenge">challenge</a>
* property specifies a challenge that the authenticator signs, along with other data,
* when producing an authentication assertion.
* @return the challenge
*/
public Bytes getChallenge() {
return this.challenge;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-timeout">timeout</a>
* property is an OPTIONAL member specifies a time, in milliseconds, that the Relying
* Party is willing to wait for the call to complete.
* @return the timeout
*/
public Duration getTimeout() {
return this.timeout;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-rpid">rpId</a>
* is an OPTIONAL member specifies the RP ID claimed by the Relying Party. The client
* MUST verify that the Relying Party's origin matches the scope of this RP ID.
* @return the relying party id
*/
public String getRpId() {
return this.rpId;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-allowcredentials">allowCredentials</a>
* property is an OPTIONAL member is used by the client to find authenticators
* eligible for this authentication ceremony.
* @return the allowCredentials property
*/
public List<PublicKeyCredentialDescriptor> getAllowCredentials() {
return this.allowCredentials;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-userverification">userVerification</a>
* property is an OPTIONAL member specifies the Relying Party's requirements regarding
* user verification for the get() operation.
* @return the user verification
*/
public UserVerificationRequirement getUserVerification() {
return this.userVerification;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptions-extensions">extensions</a>
* is an OPTIONAL property used by the Relying Party to provide client extension
* inputs requesting additional processing by the client and authenticator.
* @return the extensions
*/
public AuthenticationExtensionsClientInputs getExtensions() {
return this.extensions;
}
/**
* Creates a {@link PublicKeyCredentialRequestOptionsBuilder}
* @return the {@link PublicKeyCredentialRequestOptionsBuilder}
*/
public static PublicKeyCredentialRequestOptionsBuilder builder() {
return new PublicKeyCredentialRequestOptionsBuilder();
}
/**
* Used to build a {@link PublicKeyCredentialCreationOptions}.
*
* @author Rob Winch
* @since 6.4
*/
public static final class PublicKeyCredentialRequestOptionsBuilder {
private Bytes challenge;
private Duration timeout = Duration.ofMinutes(5);
private String rpId;
private List<PublicKeyCredentialDescriptor> allowCredentials = Collections.emptyList();
private UserVerificationRequirement userVerification;
private AuthenticationExtensionsClientInputs extensions = new ImmutableAuthenticationExtensionsClientInputs(
new ArrayList<>());
private PublicKeyCredentialRequestOptionsBuilder() {
}
/**
* Sets the {@link #getChallenge()} property.
* @param challenge the challenge
* @return the {@link PublicKeyCredentialRequestOptionsBuilder}
*/
public PublicKeyCredentialRequestOptionsBuilder challenge(Bytes challenge) {
this.challenge = challenge;
return this;
}
/**
* Sets the {@link #getTimeout()} property.
* @param timeout the timeout
* @return the {@link PublicKeyCredentialRequestOptionsBuilder}
*/
public PublicKeyCredentialRequestOptionsBuilder timeout(Duration timeout) {
Assert.notNull(timeout, "timeout cannot be null");
this.timeout = timeout;
return this;
}
/**
* Sets the {@link #getRpId()} property.
* @param rpId the rpId property
* @return the {@link PublicKeyCredentialRequestOptionsBuilder}
*/
public PublicKeyCredentialRequestOptionsBuilder rpId(String rpId) {
this.rpId = rpId;
return this;
}
/**
* Sets the {@link #getAllowCredentials()} property
* @param allowCredentials the allowed credentials
* @return the {@link PublicKeyCredentialRequestOptionsBuilder}
*/
public PublicKeyCredentialRequestOptionsBuilder allowCredentials(
List<PublicKeyCredentialDescriptor> allowCredentials) {
Assert.notNull(allowCredentials, "allowCredentials cannot be null");
this.allowCredentials = allowCredentials;
return this;
}
/**
* Sets the {@link #getUserVerification()} property.
* @param userVerification the user verification
* @return the {@link PublicKeyCredentialRequestOptionsBuilder}
*/
public PublicKeyCredentialRequestOptionsBuilder userVerification(UserVerificationRequirement userVerification) {
this.userVerification = userVerification;
return this;
}
/**
* Sets the {@link #getExtensions()} property
* @param extensions the extensions
* @return the {@link PublicKeyCredentialRequestOptionsBuilder}
*/
public PublicKeyCredentialRequestOptionsBuilder extensions(AuthenticationExtensionsClientInputs extensions) {
this.extensions = extensions;
return this;
}
/**
* Allows customizing the {@link PublicKeyCredentialRequestOptionsBuilder}
* @param customizer the {@link Consumer} used to customize the builder
* @return the {@link PublicKeyCredentialRequestOptionsBuilder}
*/
public PublicKeyCredentialRequestOptionsBuilder customize(
Consumer<PublicKeyCredentialRequestOptionsBuilder> customizer) {
customizer.accept(this);
return this;
}
/**
* Builds a new {@link PublicKeyCredentialRequestOptions}
* @return a new {@link PublicKeyCredentialRequestOptions}
*/
public PublicKeyCredentialRequestOptions build() {
if (this.challenge == null) {
this.challenge = Bytes.random();
}
return new PublicKeyCredentialRequestOptions(this.challenge, this.timeout, this.rpId, this.allowCredentials,
this.userVerification, this.extensions);
}
}
}
@@ -1,115 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialrpentity">PublicKeyCredentialRpEntity</a>
* dictionary is used to supply additional Relying Party attributes when creating a new
* credential.
*
* @author Rob Winch
* @since 6.4
*/
public final class PublicKeyCredentialRpEntity {
private final String name;
private final String id;
private PublicKeyCredentialRpEntity(String name, String id) {
this.name = name;
this.id = id;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialentity-name">name</a>
* property is a human-palatable name for the entity. Its function depends on what the
* PublicKeyCredentialEntity represents for the Relying Party, intended only for
* display.
* @return the name
*/
public String getName() {
return this.name;
}
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrpentity-id">id</a>
* property is a unique identifier for the Relying Party entity, which sets the
* <a href="https://www.w3.org/TR/webauthn-3/#rp-id">RP ID</a>.
* @return the relying party id
*/
public String getId() {
return this.id;
}
/**
* Creates a new {@link PublicKeyCredentialRpEntityBuilder}
* @return a new {@link PublicKeyCredentialRpEntityBuilder}
*/
public static PublicKeyCredentialRpEntityBuilder builder() {
return new PublicKeyCredentialRpEntityBuilder();
}
/**
* Used to create a {@link PublicKeyCredentialRpEntity}.
*
* @author Rob Winch
* @since 6.4
*/
public static final class PublicKeyCredentialRpEntityBuilder {
private String name;
private String id;
private PublicKeyCredentialRpEntityBuilder() {
}
/**
* Sets the {@link #getName()} property.
* @param name the name property
* @return the {@link PublicKeyCredentialRpEntityBuilder}
*/
public PublicKeyCredentialRpEntityBuilder name(String name) {
this.name = name;
return this;
}
/**
* Sets the {@link #getId()} property.
* @param id the id
* @return the {@link PublicKeyCredentialRpEntityBuilder}
*/
public PublicKeyCredentialRpEntityBuilder id(String id) {
this.id = id;
return this;
}
/**
* Creates a new {@link PublicKeyCredentialRpEntity}.
* @return a new {@link PublicKeyCredentialRpEntity}.
*/
public PublicKeyCredentialRpEntity build() {
return new PublicKeyCredentialRpEntity(this.name, this.id);
}
}
}
@@ -1,61 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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/#enum-credentialType">PublicKeyCredentialType</a>
* defines the credential types.
*
* @author Rob Winch
* @since 6.4
*/
public final class PublicKeyCredentialType implements Serializable {
@Serial
private static final long serialVersionUID = 7025333122210061679L;
/**
* The only credential type that currently exists.
*/
public static final PublicKeyCredentialType PUBLIC_KEY = new PublicKeyCredentialType("public-key");
private final String value;
private PublicKeyCredentialType(String value) {
this.value = value;
}
/**
* Gets the value.
* @return the value
*/
public String getValue() {
return this.value;
}
public static PublicKeyCredentialType valueOf(String value) {
if (PUBLIC_KEY.getValue().equals(value)) {
return PUBLIC_KEY;
}
return new PublicKeyCredentialType(value);
}
}
@@ -1,59 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
import java.io.Serializable;
/**
* <a href=
* "https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialuserentity">PublicKeyCredentialUserEntity</a>
* is used to supply additional
* <a href="https://www.w3.org/TR/webauthn-3/#user-account">user account</a> attributes
* when creating a new credential.
*
* @author Rob Winch
* @since 6.4
* @see org.springframework.security.web.webauthn.management.WebAuthnRelyingPartyOperations#authenticate(org.springframework.security.web.webauthn.management.RelyingPartyAuthenticationRequest)
*/
public interface PublicKeyCredentialUserEntity extends Serializable {
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialentity-name">name</a>
* property is a human-palatable identifier for a user account.
* @return the name
*/
String getName();
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialuserentity-id">id</a> is
* the user handle of the user account. A user handle is an opaque byte sequence with
* a maximum size of 64 bytes, and is not meant to be displayed to the user.
* @return the user handle of the user account
*/
Bytes getId();
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialuserentity-displayname">displayName</a>
* is a human-palatable name for the user account, intended only for display.
* @return the display name
*/
String getDisplayName();
}
@@ -1,80 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#enumdef-residentkeyrequirement">ResidentKeyRequirement</a>
* describes the Relying Partys requirements for client-side discoverable credentials.
*
* @author Rob Winch
* @since 6.4
*/
public final class ResidentKeyRequirement {
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-residentkeyrequirement-discouraged">discouraged</a>
* requirement indicates that the Relying Party prefers creating a server-side
* credential, but will accept a client-side discoverable credential.
*/
public static final ResidentKeyRequirement DISCOURAGED = new ResidentKeyRequirement("discouraged");
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-residentkeyrequirement-preferred">preferred</a>
* requirement indicates that the Relying Party strongly prefers creating a
* client-side discoverable credential, but will accept a server-side credential.
*/
public static final ResidentKeyRequirement PREFERRED = new ResidentKeyRequirement("preferred");
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-residentkeyrequirement-required">required</a>
* value indicates that the Relying Party requires a client-side discoverable
* credential.
*/
public static final ResidentKeyRequirement REQUIRED = new ResidentKeyRequirement("required");
private final String value;
private ResidentKeyRequirement(String value) {
this.value = value;
}
/**
* Gets the value.
* @return the value
*/
public String getValue() {
return this.value;
}
public static ResidentKeyRequirement valueOf(String value) {
if (DISCOURAGED.getValue().equals(value)) {
return DISCOURAGED;
}
if (PREFERRED.getValue().equals(value)) {
return PREFERRED;
}
if (REQUIRED.getValue().equals(value)) {
return REQUIRED;
}
return new ResidentKeyRequirement(value);
}
}
@@ -1,75 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.api;
import java.io.Serial;
import java.io.Serializable;
/**
* <a href=
* "https://www.w3.org/TR/webauthn-3/#enumdef-userverificationrequirement">UserVerificationRequirement</a>
* is used by the Relying Party to indicate if user verification is needed.
*
* @author Rob Winch
* @since 6.4
*/
public final class UserVerificationRequirement implements Serializable {
@Serial
private static final long serialVersionUID = -2801001231345540040L;
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-userverificationrequirement-discouraged">discouraged</a>
* value indicates that the Relying Party does not want user verification employed
* during the operation (e.g., in the interest of minimizing disruption to the user
* interaction flow).
*/
public static final UserVerificationRequirement DISCOURAGED = new UserVerificationRequirement("discouraged");
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-userverificationrequirement-preferred">preferred</a>
* value indicates that the Relying Party prefers user verification for the operation
* if possible, but will not fail the operation if the response does not have the UV
* flag set.
*/
public static final UserVerificationRequirement PREFERRED = new UserVerificationRequirement("preferred");
/**
* The <a href=
* "https://www.w3.org/TR/webauthn-3/#dom-userverificationrequirement-required">required</a>
* value indicates that the Relying Party requires user verification for the operation
* and will fail the overall ceremony if the response does not have the UV flag set.
*/
public static final UserVerificationRequirement REQUIRED = new UserVerificationRequirement("required");
private final String value;
UserVerificationRequirement(String value) {
this.value = value;
}
/**
* Gets the value
* @return the value
*/
public String getValue() {
return this.value;
}
}
@@ -1,63 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.authentication;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions;
import org.springframework.util.Assert;
/**
* A {@link PublicKeyCredentialRequestOptionsRepository} that stores the
* {@link PublicKeyCredentialRequestOptions} in the
* {@link jakarta.servlet.http.HttpSession}.
*
* @author Rob Winch
* @since 6.4
*/
public class HttpSessionPublicKeyCredentialRequestOptionsRepository
implements PublicKeyCredentialRequestOptionsRepository {
static final String DEFAULT_ATTR_NAME = PublicKeyCredentialRequestOptionsRepository.class.getName()
.concat(".ATTR_NAME");
private String attrName = DEFAULT_ATTR_NAME;
@Override
public void save(HttpServletRequest request, HttpServletResponse response,
PublicKeyCredentialRequestOptions options) {
HttpSession session = request.getSession();
session.setAttribute(this.attrName, options);
}
@Override
public PublicKeyCredentialRequestOptions load(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return null;
}
return (PublicKeyCredentialRequestOptions) session.getAttribute(this.attrName);
}
public void setAttrName(String attrName) {
Assert.notNull(attrName, "attrName cannot be null");
this.attrName = attrName;
}
}
@@ -1,138 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.authentication;
import java.io.IOException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions;
import org.springframework.security.web.webauthn.jackson.WebauthnJackson2Module;
import org.springframework.security.web.webauthn.management.ImmutablePublicKeyCredentialRequestOptionsRequest;
import org.springframework.security.web.webauthn.management.WebAuthnRelyingPartyOperations;
import org.springframework.util.Assert;
import org.springframework.web.filter.OncePerRequestFilter;
/**
* A {@link jakarta.servlet.Filter} that renders the
* {@link PublicKeyCredentialRequestOptions} in order to <a href=
* "https://w3c.github.io/webappsec-credential-management/#dom-credentialscontainer-get">get</a>
* a credential.
*
* @author Rob Winch
* @since 6.4
*/
public class PublicKeyCredentialRequestOptionsFilter extends OncePerRequestFilter {
private RequestMatcher matcher = PathPatternRequestMatcher.withDefaults()
.matcher(HttpMethod.POST, "/webauthn/authenticate/options");
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
.getContextHolderStrategy();
private final WebAuthnRelyingPartyOperations rpOptions;
private PublicKeyCredentialRequestOptionsRepository requestOptionsRepository = new HttpSessionPublicKeyCredentialRequestOptionsRepository();
private HttpMessageConverter<Object> converter = new MappingJackson2HttpMessageConverter(
Jackson2ObjectMapperBuilder.json().modules(new WebauthnJackson2Module()).build());
/**
* Creates a new instance with the provided {@link WebAuthnRelyingPartyOperations}.
* @param rpOptions the {@link WebAuthnRelyingPartyOperations} to use. Cannot be null.
*/
public PublicKeyCredentialRequestOptionsFilter(WebAuthnRelyingPartyOperations rpOptions) {
Assert.notNull(rpOptions, "rpOperations cannot be null");
this.rpOptions = rpOptions;
}
/**
* Sets the {@link RequestMatcher} used to trigger this filter. By default, the
* {@link RequestMatcher} is {@code POST /webauthn/authenticate/options}.
* @param requestMatcher the {@link RequestMatcher} to use
* @since 6.5
*/
public void setRequestMatcher(RequestMatcher requestMatcher) {
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
this.matcher = requestMatcher;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (!this.matcher.matches(request)) {
filterChain.doFilter(request, response);
return;
}
SecurityContext context = this.securityContextHolderStrategy.getContext();
ImmutablePublicKeyCredentialRequestOptionsRequest optionsRequest = new ImmutablePublicKeyCredentialRequestOptionsRequest(
context.getAuthentication());
PublicKeyCredentialRequestOptions credentialRequestOptions = this.rpOptions
.createCredentialRequestOptions(optionsRequest);
this.requestOptionsRepository.save(request, response, credentialRequestOptions);
response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
this.converter.write(credentialRequestOptions, MediaType.APPLICATION_JSON,
new ServletServerHttpResponse(response));
}
/**
* Sets the {@link PublicKeyCredentialRequestOptionsRepository} to use.
* @param requestOptionsRepository the
* {@link PublicKeyCredentialRequestOptionsRepository} to use. Cannot be null.
*/
public void setRequestOptionsRepository(PublicKeyCredentialRequestOptionsRepository requestOptionsRepository) {
Assert.notNull(requestOptionsRepository, "requestOptionsRepository cannot be null");
this.requestOptionsRepository = requestOptionsRepository;
}
/**
* Sets the {@link HttpMessageConverter} to use.
* @param converter the {@link HttpMessageConverter} to use. Cannot be null.
*/
public void setConverter(HttpMessageConverter<Object> converter) {
Assert.notNull(converter, "converter cannot be null");
this.converter = converter;
}
/**
* Sets the {@link SecurityContextHolderStrategy} to use.
* @param securityContextHolderStrategy the {@link SecurityContextHolderStrategy} to
* use. Cannot be null.
*/
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null");
this.securityContextHolderStrategy = securityContextHolderStrategy;
}
}
@@ -1,52 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.authentication;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions;
/**
* Saves {@link PublicKeyCredentialRequestOptions} between a request to generate an
* assertion and the validation of the assertion.
*
* @author Rob Winch
* @since 6.4
*/
public interface PublicKeyCredentialRequestOptionsRepository {
/**
* Saves the provided {@link PublicKeyCredentialRequestOptions} or clears an existing
* {@link PublicKeyCredentialRequestOptions} if {@code options} is null.
* @param request the {@link HttpServletRequest}
* @param response the {@link HttpServletResponse}
* @param options the {@link PublicKeyCredentialRequestOptions} to save or null if an
* existing {@link PublicKeyCredentialRequestOptions} should be removed.
*/
void save(HttpServletRequest request, HttpServletResponse response, PublicKeyCredentialRequestOptions options);
/**
* Gets a saved {@link PublicKeyCredentialRequestOptions} if it exists, otherwise
* null.
* @param request the {@link HttpServletRequest}
* @return the {@link PublicKeyCredentialRequestOptions} that was saved, otherwise
* null.
*/
PublicKeyCredentialRequestOptions load(HttpServletRequest request);
}
@@ -1,70 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.authentication;
import java.io.Serial;
import java.util.Collection;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
import org.springframework.util.Assert;
/**
* A {@link WebAuthnAuthentication} is used to represent successful authentication with
* WebAuthn.
*
* @author Rob Winch
* @since 6.4
* @see WebAuthnAuthenticationRequestToken
*/
public class WebAuthnAuthentication extends AbstractAuthenticationToken {
@Serial
private static final long serialVersionUID = -4879907158750659197L;
private final PublicKeyCredentialUserEntity principal;
public WebAuthnAuthentication(PublicKeyCredentialUserEntity principal,
Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
super.setAuthenticated(true);
}
@Override
public void setAuthenticated(boolean authenticated) {
Assert.isTrue(!authenticated, "Cannot set this token to trusted");
super.setAuthenticated(authenticated);
}
@Override
public Object getCredentials() {
return null;
}
@Override
public PublicKeyCredentialUserEntity getPrincipal() {
return this.principal;
}
@Override
public String getName() {
return this.principal.getName();
}
}
@@ -1,136 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.authentication;
import java.io.IOException;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.core.ResolvableType;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationEntryPointFailureHandler;
import org.springframework.security.web.authentication.HttpMessageConverterAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.webauthn.api.AuthenticatorAssertionResponse;
import org.springframework.security.web.webauthn.api.PublicKeyCredential;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions;
import org.springframework.security.web.webauthn.jackson.WebauthnJackson2Module;
import org.springframework.security.web.webauthn.management.RelyingPartyAuthenticationRequest;
import org.springframework.util.Assert;
import static org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher.pathPattern;
/**
* Authenticates {@code PublicKeyCredential<AuthenticatorAssertionResponse>} that is
* parsed from the body of the {@link HttpServletRequest} using the
* {@link #setConverter(GenericHttpMessageConverter)}. An example request is provided
* below:
*
* <pre>
* {
* "id": "dYF7EGnRFFIXkpXi9XU2wg",
* "rawId": "dYF7EGnRFFIXkpXi9XU2wg",
* "response": {
* "authenticatorData": "y9GqwTRaMpzVDbXq1dyEAXVOxrou08k22ggRC45MKNgdAAAAAA",
* "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiRFVsRzRDbU9naWhKMG1vdXZFcE9HdUk0ZVJ6MGRRWmxUQmFtbjdHQ1FTNCIsIm9yaWdpbiI6Imh0dHBzOi8vZXhhbXBsZS5sb2NhbGhvc3Q6ODQ0MyIsImNyb3NzT3JpZ2luIjpmYWxzZX0",
* "signature": "MEYCIQCW2BcUkRCAXDmGxwMi78jknenZ7_amWrUJEYoTkweldAIhAMD0EMp1rw2GfwhdrsFIeDsL7tfOXVPwOtfqJntjAo4z",
* "userHandle": "Q3_0Xd64_HW0BlKRAJnVagJTpLKLgARCj8zjugpRnVo"
* },
* "clientExtensionResults": {},
* "authenticatorAttachment": "platform"
* }
* </pre>
*
* @author Rob Winch
* @since 6.4
*/
public class WebAuthnAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private GenericHttpMessageConverter<Object> converter = new MappingJackson2HttpMessageConverter(
Jackson2ObjectMapperBuilder.json().modules(new WebauthnJackson2Module()).build());
private PublicKeyCredentialRequestOptionsRepository requestOptionsRepository = new HttpSessionPublicKeyCredentialRequestOptionsRepository();
public WebAuthnAuthenticationFilter() {
super(pathPattern(HttpMethod.POST, "/login/webauthn"));
setSecurityContextRepository(new HttpSessionSecurityContextRepository());
setAuthenticationFailureHandler(
new AuthenticationEntryPointFailureHandler(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)));
setAuthenticationSuccessHandler(new HttpMessageConverterAuthenticationSuccessHandler());
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
ServletServerHttpRequest httpRequest = new ServletServerHttpRequest(request);
ResolvableType resolvableType = ResolvableType.forClassWithGenerics(PublicKeyCredential.class,
AuthenticatorAssertionResponse.class);
PublicKeyCredential<AuthenticatorAssertionResponse> publicKeyCredential = null;
try {
publicKeyCredential = (PublicKeyCredential<AuthenticatorAssertionResponse>) this.converter
.read(resolvableType.getType(), getClass(), httpRequest);
}
catch (Exception ex) {
throw new BadCredentialsException("Unable to authenticate the PublicKeyCredential", ex);
}
PublicKeyCredentialRequestOptions requestOptions = this.requestOptionsRepository.load(request);
if (requestOptions == null) {
throw new BadCredentialsException(
"Unable to authenticate the PublicKeyCredential. No PublicKeyCredentialRequestOptions found.");
}
this.requestOptionsRepository.save(request, response, null);
RelyingPartyAuthenticationRequest authenticationRequest = new RelyingPartyAuthenticationRequest(requestOptions,
publicKeyCredential);
WebAuthnAuthenticationRequestToken token = new WebAuthnAuthenticationRequestToken(authenticationRequest);
return getAuthenticationManager().authenticate(token);
}
/**
* Sets the {@link GenericHttpMessageConverter} to use for writing
* {@code PublicKeyCredential<AuthenticatorAssertionResponse>} to the response. The
* default is @{code MappingJackson2HttpMessageConverter}
* @param converter the {@link GenericHttpMessageConverter} to use. Cannot be null.
*/
public void setConverter(GenericHttpMessageConverter<Object> converter) {
Assert.notNull(converter, "converter cannot be null");
this.converter = converter;
}
/**
* Sets the {@link PublicKeyCredentialRequestOptionsRepository} to use. The default is
* {@link HttpSessionPublicKeyCredentialRequestOptionsRepository}.
* @param requestOptionsRepository the
* {@link PublicKeyCredentialRequestOptionsRepository} to use. Cannot be null.
*/
public void setRequestOptionsRepository(PublicKeyCredentialRequestOptionsRepository requestOptionsRepository) {
Assert.notNull(requestOptionsRepository, "requestOptionsRepository cannot be null");
this.requestOptionsRepository = requestOptionsRepository;
}
}
@@ -1,80 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.authentication;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
import org.springframework.security.web.webauthn.management.RelyingPartyAuthenticationRequest;
import org.springframework.security.web.webauthn.management.WebAuthnRelyingPartyOperations;
import org.springframework.util.Assert;
/**
* An {@link AuthenticationProvider} that uses {@link WebAuthnRelyingPartyOperations} for
* authentication using an {@link WebAuthnAuthenticationRequestToken}. First
* {@link WebAuthnRelyingPartyOperations#authenticate(RelyingPartyAuthenticationRequest)}
* is invoked. The result is a username passed into {@link UserDetailsService}. The
* {@link UserDetails} is used to create an {@link Authentication}.
*
* @author Rob Winch
* @since 6.4
*/
public class WebAuthnAuthenticationProvider implements AuthenticationProvider {
private final WebAuthnRelyingPartyOperations relyingPartyOperations;
private final UserDetailsService userDetailsService;
/**
* Creates a new instance.
* @param relyingPartyOperations the {@link WebAuthnRelyingPartyOperations} to use.
* Cannot be null.
* @param userDetailsService the {@link UserDetailsService} to use. Cannot be null.
*/
public WebAuthnAuthenticationProvider(WebAuthnRelyingPartyOperations relyingPartyOperations,
UserDetailsService userDetailsService) {
Assert.notNull(relyingPartyOperations, "relyingPartyOperations cannot be null");
Assert.notNull(userDetailsService, "userDetailsService cannot be null");
this.relyingPartyOperations = relyingPartyOperations;
this.userDetailsService = userDetailsService;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
WebAuthnAuthenticationRequestToken webAuthnRequest = (WebAuthnAuthenticationRequestToken) authentication;
try {
PublicKeyCredentialUserEntity userEntity = this.relyingPartyOperations
.authenticate(webAuthnRequest.getWebAuthnRequest());
String username = userEntity.getName();
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
return new WebAuthnAuthentication(userEntity, userDetails.getAuthorities());
}
catch (RuntimeException ex) {
throw new BadCredentialsException(ex.getMessage(), ex);
}
}
@Override
public boolean supports(Class<?> authentication) {
return WebAuthnAuthenticationRequestToken.class.isAssignableFrom(authentication);
}
}
@@ -1,75 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.authentication;
import java.io.Serial;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.web.webauthn.management.RelyingPartyAuthenticationRequest;
import org.springframework.util.Assert;
/**
* An {@link org.springframework.security.core.Authentication} used in
* {@link WebAuthnAuthenticationProvider} for authenticating via WebAuthn.
*
* @author Rob Winch
* @since 6.4
*/
public class WebAuthnAuthenticationRequestToken extends AbstractAuthenticationToken {
@Serial
private static final long serialVersionUID = -1682693433877522403L;
private final RelyingPartyAuthenticationRequest webAuthnRequest;
/**
* Creates a new instance.
* @param webAuthnRequest the {@link RelyingPartyAuthenticationRequest} to use for
* authentication. Cannot be null.
*/
public WebAuthnAuthenticationRequestToken(RelyingPartyAuthenticationRequest webAuthnRequest) {
super(AuthorityUtils.NO_AUTHORITIES);
Assert.notNull(webAuthnRequest, "webAuthnRequest cannot be null");
this.webAuthnRequest = webAuthnRequest;
}
/**
* Gets the {@link RelyingPartyAuthenticationRequest}
* @return the {@link RelyingPartyAuthenticationRequest}
*/
public RelyingPartyAuthenticationRequest getWebAuthnRequest() {
return this.webAuthnRequest;
}
@Override
public void setAuthenticated(boolean authenticated) {
Assert.isTrue(!authenticated, "Cannot set this token to trusted");
super.setAuthenticated(authenticated);
}
@Override
public Object getCredentials() {
return this.webAuthnRequest.getPublicKey();
}
@Override
public Object getPrincipal() {
return this.webAuthnRequest.getPublicKey().getResponse().getUserHandle();
}
}
@@ -1,32 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.springframework.security.web.webauthn.api.AttestationConveyancePreference;
/**
* Jackson mixin for {@link AttestationConveyancePreference}
*
* @author Rob Winch
* @since 6.4
*/
@JsonSerialize(using = AttestationConveyancePreferenceSerializer.class)
class AttestationConveyancePreferenceMixin {
}
@@ -1,46 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import org.springframework.security.web.webauthn.api.AttestationConveyancePreference;
/**
* Jackson serializer for {@link AttestationConveyancePreference}
*
* @author Rob Winch
* @since 6.4
*/
@SuppressWarnings("serial")
class AttestationConveyancePreferenceSerializer extends StdSerializer<AttestationConveyancePreference> {
AttestationConveyancePreferenceSerializer() {
super(AttestationConveyancePreference.class);
}
@Override
public void serialize(AttestationConveyancePreference preference, JsonGenerator jgen, SerializerProvider provider)
throws IOException {
jgen.writeString(preference.getValue());
}
}
@@ -1,32 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInputs;
/**
* Jackson mixin for {@link AuthenticationExtensionsClientInputs}
*
* @author Rob Winch
* @since 6.4
*/
@JsonSerialize(using = AuthenticationExtensionsClientInputSerializer.class)
class AuthenticationExtensionsClientInputMixin {
}
@@ -1,49 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInput;
/**
* Provides Jackson serialization of {@link AuthenticationExtensionsClientInput}.
*
* @author Rob Winch
* @since 6.4
*/
@SuppressWarnings("serial")
class AuthenticationExtensionsClientInputSerializer extends StdSerializer<AuthenticationExtensionsClientInput> {
/**
* Creates a new instance.
*/
AuthenticationExtensionsClientInputSerializer() {
super(AuthenticationExtensionsClientInput.class);
}
@Override
public void serialize(AuthenticationExtensionsClientInput input, JsonGenerator jgen, SerializerProvider provider)
throws IOException {
jgen.writeObjectField(input.getExtensionId(), input.getInput());
}
}
@@ -1,32 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInputs;
/**
* Jackson mixin for {@link AuthenticationExtensionsClientInputs}
*
* @author Rob Winch
* @since 6.4
*/
@JsonSerialize(using = AuthenticationExtensionsClientInputsSerializer.class)
class AuthenticationExtensionsClientInputsMixin {
}
@@ -1,54 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInput;
import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInputs;
/**
* Provides Jackson serialization of {@link AuthenticationExtensionsClientInputs}.
*
* @author Rob Winch
* @since 6.4
*/
@SuppressWarnings("serial")
class AuthenticationExtensionsClientInputsSerializer extends StdSerializer<AuthenticationExtensionsClientInputs> {
/**
* Creates a new instance.
*/
AuthenticationExtensionsClientInputsSerializer() {
super(AuthenticationExtensionsClientInputs.class);
}
@Override
public void serialize(AuthenticationExtensionsClientInputs inputs, JsonGenerator jgen, SerializerProvider provider)
throws IOException {
jgen.writeStartObject();
for (AuthenticationExtensionsClientInput input : inputs.getInputs()) {
jgen.writeObject(input);
}
jgen.writeEndObject();
}
}
@@ -1,78 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientOutput;
import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientOutputs;
import org.springframework.security.web.webauthn.api.CredentialPropertiesOutput;
import org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientOutputs;
/**
* Provides Jackson deserialization of {@link AuthenticationExtensionsClientOutputs}.
*
* @author Rob Winch
* @since 6.4
*/
@SuppressWarnings("serial")
class AuthenticationExtensionsClientOutputsDeserializer extends StdDeserializer<AuthenticationExtensionsClientOutputs> {
private static final Log logger = LogFactory.getLog(AuthenticationExtensionsClientOutputsDeserializer.class);
/**
* Creates a new instance.
*/
AuthenticationExtensionsClientOutputsDeserializer() {
super(AuthenticationExtensionsClientOutputs.class);
}
@Override
public AuthenticationExtensionsClientOutputs deserialize(JsonParser parser, DeserializationContext ctxt)
throws IOException, JacksonException {
List<AuthenticationExtensionsClientOutput<?>> 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)) {
CredentialPropertiesOutput output = parser.readValueAs(CredentialPropertiesOutput.class);
outputs.add(output);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Skipping unknown extension with id " + key);
}
parser.nextValue();
}
}
return new ImmutableAuthenticationExtensionsClientOutputs(outputs);
}
}
@@ -1,32 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientOutputs;
/**
* Jackson mixin for {@link AuthenticationExtensionsClientOutputs}
*
* @author Rob Winch
* @since 6.4
*/
@JsonDeserialize(using = AuthenticationExtensionsClientOutputsDeserializer.class)
class AuthenticationExtensionsClientOutputsMixin {
}
@@ -1,38 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import org.springframework.security.web.webauthn.api.AuthenticatorAssertionResponse;
/**
* Jackson mixin for {@link AuthenticatorAssertionResponse}
*
* @author Rob Winch
* @since 6.4
*/
@JsonDeserialize(builder = AuthenticatorAssertionResponse.AuthenticatorAssertionResponseBuilder.class)
class AuthenticatorAssertionResponseMixin {
@JsonPOJOBuilder(withPrefix = "")
abstract class AuthenticatorAssertionResponseBuilderMixin {
}
}
@@ -1,53 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import java.io.IOException;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import org.springframework.security.web.webauthn.api.AuthenticatorAttachment;
/**
* Jackson deserializer for {@link AuthenticatorAttachment}
*
* @author Rob Winch
* @since 6.4
*/
@SuppressWarnings("serial")
class AuthenticatorAttachmentDeserializer extends StdDeserializer<AuthenticatorAttachment> {
AuthenticatorAttachmentDeserializer() {
super(AuthenticatorAttachment.class);
}
@Override
public AuthenticatorAttachment deserialize(JsonParser parser, DeserializationContext ctxt)
throws IOException, JacksonException {
String type = parser.readValueAs(String.class);
for (AuthenticatorAttachment publicKeyCredentialType : AuthenticatorAttachment.values()) {
if (publicKeyCredentialType.getValue().equals(type)) {
return publicKeyCredentialType;
}
}
return null;
}
}
@@ -1,34 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.springframework.security.web.webauthn.api.AuthenticatorAttachment;
/**
* Jackson mixin for {@link AuthenticatorAttachment}
*
* @author Rob Winch
* @since 6.4
*/
@JsonDeserialize(using = AuthenticatorAttachmentDeserializer.class)
@JsonSerialize(using = AuthenticatorAttachmentSerializer.class)
class AuthenticatorAttachmentMixin {
}
@@ -1,46 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import org.springframework.security.web.webauthn.api.AuthenticatorAttachment;
/**
* Jackson serializer for {@link AuthenticatorAttachment}
*
* @author Rob Winch
* @since 6.4
*/
@SuppressWarnings("serial")
class AuthenticatorAttachmentSerializer extends StdSerializer<AuthenticatorAttachment> {
AuthenticatorAttachmentSerializer() {
super(AuthenticatorAttachment.class);
}
@Override
public void serialize(AuthenticatorAttachment attachment, JsonGenerator jgen, SerializerProvider provider)
throws IOException {
jgen.writeString(attachment.getValue());
}
}
@@ -1,48 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import org.springframework.security.web.webauthn.api.AuthenticatorAttestationResponse;
import org.springframework.security.web.webauthn.api.AuthenticatorTransport;
/**
* Jackson mixin for {@link AuthenticatorAttestationResponse}
*
* @author Rob Winch
* @since 6.4
*/
@JsonDeserialize(builder = AuthenticatorAttestationResponse.AuthenticatorAttestationResponseBuilder.class)
class AuthenticatorAttestationResponseMixin {
@JsonPOJOBuilder(withPrefix = "")
@JsonIgnoreProperties(ignoreUnknown = true)
abstract class AuthenticatorAttestationResponseBuilderMixin {
@JsonSetter
abstract AuthenticatorAttestationResponse.AuthenticatorAttestationResponseBuilder transports(
List<AuthenticatorTransport> transports);
}
}
@@ -1,32 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria;
/**
* Jackson mixin for {@link AuthenticatorSelectionCriteria}
*
* @author Rob Winch
* @since 6.4
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
abstract class AuthenticatorSelectionCriteriaMixin {
}
@@ -1,53 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import java.io.IOException;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import org.springframework.security.web.webauthn.api.AuthenticatorTransport;
/**
* Jackson deserializer for {@link AuthenticatorTransport}
*
* @author Rob Winch
* @since 6.4
*/
@SuppressWarnings("serial")
class AuthenticatorTransportDeserializer extends StdDeserializer<AuthenticatorTransport> {
AuthenticatorTransportDeserializer() {
super(AuthenticatorTransport.class);
}
@Override
public AuthenticatorTransport deserialize(JsonParser parser, DeserializationContext ctxt)
throws IOException, JacksonException {
String transportValue = parser.readValueAs(String.class);
for (AuthenticatorTransport transport : AuthenticatorTransport.values()) {
if (transport.getValue().equals(transportValue)) {
return transport;
}
}
return null;
}
}
@@ -1,34 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.springframework.security.web.webauthn.api.AuthenticatorTransport;
/**
* Jackson mixin for {@link AuthenticatorTransport}
*
* @author Rob Winch
* @since 6.4
*/
@JsonDeserialize(using = AuthenticatorTransportDeserializer.class)
@JsonSerialize(using = AuthenticatorTransportSerializer.class)
class AuthenticatorTransportMixin {
}
@@ -1,41 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import org.springframework.security.web.webauthn.api.AuthenticatorTransport;
/**
* Jackson serializer for {@link AuthenticatorTransport}
*
* @author Rob Winch
* @since 6.4
*/
class AuthenticatorTransportSerializer extends JsonSerializer<AuthenticatorTransport> {
@Override
public void serialize(AuthenticatorTransport transport, JsonGenerator jgen, SerializerProvider provider)
throws IOException {
jgen.writeString(transport.getValue());
}
}
@@ -1,41 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.springframework.security.web.webauthn.api.Bytes;
/**
* Jackson mixin for {@link Bytes}
*
* @author Rob Winch
* @since 6.4
*/
@JsonSerialize(using = BytesSerializer.class)
final class BytesMixin {
@JsonCreator
static Bytes fromBase64(String value) {
return Bytes.fromBase64(value);
}
private BytesMixin() {
}
}
@@ -1,48 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import org.springframework.security.web.webauthn.api.Bytes;
/**
* Jackson serializer for {@link Bytes}
*
* @author Rob Winch
* @since 6.4
*/
@SuppressWarnings("serial")
class BytesSerializer extends StdSerializer<Bytes> {
/**
* Creates a new instance.
*/
BytesSerializer() {
super(Bytes.class);
}
@Override
public void serialize(Bytes bytes, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeString(bytes.toBase64UrlString());
}
}
@@ -1,53 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import java.io.IOException;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import org.springframework.security.web.webauthn.api.COSEAlgorithmIdentifier;
/**
* Jackson serializer for {@link COSEAlgorithmIdentifier}
*
* @author Rob Winch
* @since 6.4
*/
@SuppressWarnings("serial")
class COSEAlgorithmIdentifierDeserializer extends StdDeserializer<COSEAlgorithmIdentifier> {
COSEAlgorithmIdentifierDeserializer() {
super(COSEAlgorithmIdentifier.class);
}
@Override
public COSEAlgorithmIdentifier deserialize(JsonParser parser, DeserializationContext ctxt)
throws IOException, JacksonException {
Long transportValue = parser.readValueAs(Long.class);
for (COSEAlgorithmIdentifier identifier : COSEAlgorithmIdentifier.values()) {
if (identifier.getValue() == transportValue.longValue()) {
return identifier;
}
}
return null;
}
}
@@ -1,34 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.springframework.security.web.webauthn.api.COSEAlgorithmIdentifier;
/**
* Jackson mixin for {@link COSEAlgorithmIdentifier}
*
* @author Rob Winch
* @since 6.4
*/
@JsonSerialize(using = COSEAlgorithmIdentifierSerializer.class)
@JsonDeserialize(using = COSEAlgorithmIdentifierDeserializer.class)
abstract class COSEAlgorithmIdentifierMixin {
}
@@ -1,46 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import org.springframework.security.web.webauthn.api.COSEAlgorithmIdentifier;
/**
* Jackson serializer for {@link COSEAlgorithmIdentifier}
*
* @author Rob Winch
* @since 6.4
*/
@SuppressWarnings("serial")
class COSEAlgorithmIdentifierSerializer extends StdSerializer<COSEAlgorithmIdentifier> {
COSEAlgorithmIdentifierSerializer() {
super(COSEAlgorithmIdentifier.class);
}
@Override
public void serialize(COSEAlgorithmIdentifier identifier, JsonGenerator jgen, SerializerProvider provider)
throws IOException {
jgen.writeNumber(identifier.getValue());
}
}
@@ -1,24 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
@JsonSerialize(using = CredProtectAuthenticationExtensionsClientInputSerializer.class)
class CredProtectAuthenticationExtensionsClientInputMixin {
}
@@ -1,64 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput;
/**
* Serializes <a href=
* "https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-credProtect-extension">credProtect
* extension</a>.
*
* @author Rob Winch
*/
@SuppressWarnings("serial")
class CredProtectAuthenticationExtensionsClientInputSerializer
extends StdSerializer<CredProtectAuthenticationExtensionsClientInput> {
protected CredProtectAuthenticationExtensionsClientInputSerializer() {
super(CredProtectAuthenticationExtensionsClientInput.class);
}
@Override
public void serialize(CredProtectAuthenticationExtensionsClientInput input, JsonGenerator jgen,
SerializerProvider provider) throws IOException {
CredProtectAuthenticationExtensionsClientInput.CredProtect credProtect = input.getInput();
String policy = toString(credProtect.getCredProtectionPolicy());
jgen.writeObjectField("credentialProtectionPolicy", policy);
jgen.writeObjectField("enforceCredentialProtectionPolicy", credProtect.isEnforceCredentialProtectionPolicy());
}
private static String toString(CredProtectAuthenticationExtensionsClientInput.CredProtect.ProtectionPolicy policy) {
switch (policy) {
case USER_VERIFICATION_OPTIONAL:
return "userVerificationOptional";
case USER_VERIFICATION_OPTIONAL_WITH_CREDENTIAL_ID_LIST:
return "userVerificationOptionalWithCredentialIdList";
case USER_VERIFICATION_REQUIRED:
return "userVerificationRequired";
default:
throw new IllegalArgumentException("Unsupported ProtectionPolicy " + policy);
}
}
}
@@ -1,36 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.security.web.webauthn.api.CredentialPropertiesOutput;
/**
* Jackson mixin for {@link CredentialPropertiesOutput}
*
* @author Rob Winch
* @since 6.4
*/
@JsonIgnoreProperties(ignoreUnknown = true)
abstract class CredentialPropertiesOutputMixin {
CredentialPropertiesOutputMixin(@JsonProperty("rk") boolean rk) {
}
}
@@ -1,47 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import java.io.IOException;
import java.time.Duration;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
/**
* Jackson serializer for {@link Duration}
*
* @author Rob Winch
* @since 6.4
*/
@SuppressWarnings("serial")
class DurationSerializer extends StdSerializer<Duration> {
/**
* Creates an instance.
*/
DurationSerializer() {
super(Duration.class);
}
@Override
public void serialize(Duration duration, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeNumber(duration.toMillis());
}
}
@@ -1,38 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import java.time.Duration;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
/**
* Jackson mixin for {@link PublicKeyCredentialCreationOptions}
*
* @author Rob Winch
* @since 6.4
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
abstract class PublicKeyCredentialCreationOptionsMixin {
@JsonSerialize(using = DurationSerializer.class)
private Duration timeout;
}
@@ -1,38 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import org.springframework.security.web.webauthn.api.PublicKeyCredential;
/**
* Jackson mixin for {@link PublicKeyCredential}
*
* @author Rob Winch
* @since 6.4
*/
@JsonDeserialize(builder = PublicKeyCredential.PublicKeyCredentialBuilder.class)
class PublicKeyCredentialMixin {
@JsonPOJOBuilder(withPrefix = "")
static class PublicKeyCredentialBuilderMixin {
}
}
@@ -1,38 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import java.time.Duration;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions;
/**
* Jackson mixin for {@link PublicKeyCredentialRequestOptions}
*
* @author Rob Winch
* @since 6.4
*/
@JsonInclude(content = JsonInclude.Include.NON_NULL)
class PublicKeyCredentialRequestOptionsMixin {
@JsonSerialize(using = DurationSerializer.class)
private final Duration timeout = null;
}
@@ -1,51 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import java.io.IOException;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialType;
/**
* Jackson deserializer for {@link PublicKeyCredentialType}
*
* @author Rob Winch
* @since 6.4
*/
@SuppressWarnings("serial")
class PublicKeyCredentialTypeDeserializer extends StdDeserializer<PublicKeyCredentialType> {
/**
* Creates a new instance.
*/
PublicKeyCredentialTypeDeserializer() {
super(PublicKeyCredentialType.class);
}
@Override
public PublicKeyCredentialType deserialize(JsonParser parser, DeserializationContext ctxt)
throws IOException, JacksonException {
String type = parser.readValueAs(String.class);
return PublicKeyCredentialType.valueOf(type);
}
}
@@ -1,34 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialType;
/**
* Jackson mixin for {@link PublicKeyCredentialType}
*
* @author Rob Winch
* @since 6.4
*/
@JsonSerialize(using = PublicKeyCredentialTypeSerializer.class)
@JsonDeserialize(using = PublicKeyCredentialTypeDeserializer.class)
abstract class PublicKeyCredentialTypeMixin {
}
@@ -1,49 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialType;
/**
* Jackson serializer for {@link PublicKeyCredentialType}
*
* @author Rob Winch
* @since 6.4
*/
@SuppressWarnings("serial")
class PublicKeyCredentialTypeSerializer extends StdSerializer<PublicKeyCredentialType> {
/**
* Creates a new instance.
*/
PublicKeyCredentialTypeSerializer() {
super(PublicKeyCredentialType.class);
}
@Override
public void serialize(PublicKeyCredentialType type, JsonGenerator jgen, SerializerProvider provider)
throws IOException {
jgen.writeString(type.getValue());
}
}
@@ -1,38 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.security.web.webauthn.api.AuthenticatorAttestationResponse;
import org.springframework.security.web.webauthn.api.PublicKeyCredential;
import org.springframework.security.web.webauthn.management.RelyingPartyPublicKey;
/**
* Jackson mixin for {@link RelyingPartyPublicKey}
*
* @author Rob Winch
* @since 6.4
*/
abstract class RelyingPartyPublicKeyMixin {
RelyingPartyPublicKeyMixin(
@JsonProperty("credential") PublicKeyCredential<AuthenticatorAttestationResponse> credential,
@JsonProperty("label") String label) {
}
}
@@ -1,32 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.springframework.security.web.webauthn.api.ResidentKeyRequirement;
/**
* Jackson mixin for {@link ResidentKeyRequirement}
*
* @author Rob Winch
* @since 6.4
*/
@JsonSerialize(using = ResidentKeyRequirementSerializer.class)
abstract class ResidentKeyRequirementMixin {
}
@@ -1,49 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import org.springframework.security.web.webauthn.api.ResidentKeyRequirement;
/**
* Jackson serializer for {@link ResidentKeyRequirement}
*
* @author Rob Winch
* @since 6.4
*/
@SuppressWarnings("serial")
class ResidentKeyRequirementSerializer extends StdSerializer<ResidentKeyRequirement> {
/**
* Creates a new instance.
*/
ResidentKeyRequirementSerializer() {
super(ResidentKeyRequirement.class);
}
@Override
public void serialize(ResidentKeyRequirement requirement, JsonGenerator jgen, SerializerProvider provider)
throws IOException {
jgen.writeString(requirement.getValue());
}
}
@@ -1,32 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.springframework.security.web.webauthn.api.UserVerificationRequirement;
/**
* Jackson mixin for {@link UserVerificationRequirement}
*
* @author Rob Winch
* @since 6.4
*/
@JsonSerialize(using = UserVerificationRequirementSerializer.class)
abstract class UserVerificationRequirementMixin {
}
@@ -1,49 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import org.springframework.security.web.webauthn.api.UserVerificationRequirement;
/**
* Jackson serializer for {@link UserVerificationRequirement}
*
* @author Rob Winch
* @since 6.4
*/
@SuppressWarnings("serial")
class UserVerificationRequirementSerializer extends StdSerializer<UserVerificationRequirement> {
/**
* Creates a new instance.
*/
UserVerificationRequirementSerializer() {
super(UserVerificationRequirement.class);
}
@Override
public void serialize(UserVerificationRequirement requirement, JsonGenerator jgen, SerializerProvider provider)
throws IOException {
jgen.writeString(requirement.getValue());
}
}
@@ -1,97 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.jackson;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.security.web.webauthn.api.AttestationConveyancePreference;
import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInput;
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.AuthenticatorAttestationResponse;
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.PublicKeyCredential;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialType;
import org.springframework.security.web.webauthn.api.ResidentKeyRequirement;
import org.springframework.security.web.webauthn.api.UserVerificationRequirement;
import org.springframework.security.web.webauthn.management.RelyingPartyPublicKey;
/**
* Adds Jackson support for Spring Security WebAuthn. It is automatically registered when
* using Jackson's SPI support.
*
* @author Rob Winch
* @since 6.4
*/
@SuppressWarnings("serial")
public class WebauthnJackson2Module extends SimpleModule {
/**
* Creates a new instance.
*/
public WebauthnJackson2Module() {
super(WebauthnJackson2Module.class.getName(), new Version(1, 0, 0, null, null, null));
}
@Override
public void setupModule(SetupContext context) {
context.setMixInAnnotations(Bytes.class, BytesMixin.class);
context.setMixInAnnotations(AttestationConveyancePreference.class, AttestationConveyancePreferenceMixin.class);
context.setMixInAnnotations(AuthenticationExtensionsClientInput.class,
AuthenticationExtensionsClientInputMixin.class);
context.setMixInAnnotations(AuthenticationExtensionsClientInputs.class,
AuthenticationExtensionsClientInputsMixin.class);
context.setMixInAnnotations(AuthenticationExtensionsClientOutputs.class,
AuthenticationExtensionsClientOutputsMixin.class);
context.setMixInAnnotations(AuthenticatorAssertionResponse.AuthenticatorAssertionResponseBuilder.class,
AuthenticatorAssertionResponseMixin.AuthenticatorAssertionResponseBuilderMixin.class);
context.setMixInAnnotations(AuthenticatorAssertionResponse.class, AuthenticatorAssertionResponseMixin.class);
context.setMixInAnnotations(AuthenticatorAttachment.class, AuthenticatorAttachmentMixin.class);
context.setMixInAnnotations(AuthenticatorAttestationResponse.class,
AuthenticatorAttestationResponseMixin.class);
context.setMixInAnnotations(AuthenticatorAttestationResponse.AuthenticatorAttestationResponseBuilder.class,
AuthenticatorAttestationResponseMixin.AuthenticatorAttestationResponseBuilderMixin.class);
context.setMixInAnnotations(AuthenticatorSelectionCriteria.class, AuthenticatorSelectionCriteriaMixin.class);
context.setMixInAnnotations(AuthenticatorTransport.class, AuthenticatorTransportMixin.class);
context.setMixInAnnotations(COSEAlgorithmIdentifier.class, COSEAlgorithmIdentifierMixin.class);
context.setMixInAnnotations(CredentialPropertiesOutput.class, CredentialPropertiesOutputMixin.class);
context.setMixInAnnotations(CredProtectAuthenticationExtensionsClientInput.class,
CredProtectAuthenticationExtensionsClientInputMixin.class);
context.setMixInAnnotations(PublicKeyCredential.PublicKeyCredentialBuilder.class,
PublicKeyCredentialMixin.PublicKeyCredentialBuilderMixin.class);
context.setMixInAnnotations(PublicKeyCredential.class, PublicKeyCredentialMixin.class);
context.setMixInAnnotations(PublicKeyCredentialCreationOptions.class,
PublicKeyCredentialCreationOptionsMixin.class);
context.setMixInAnnotations(PublicKeyCredentialRequestOptions.class,
PublicKeyCredentialRequestOptionsMixin.class);
context.setMixInAnnotations(PublicKeyCredentialType.class, PublicKeyCredentialTypeMixin.class);
context.setMixInAnnotations(RelyingPartyPublicKey.class, RelyingPartyPublicKeyMixin.class);
context.setMixInAnnotations(ResidentKeyRequirement.class, ResidentKeyRequirementMixin.class);
context.setMixInAnnotations(UserVerificationRequirement.class, UserVerificationRequirementMixin.class);
}
}
@@ -1,42 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.management;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
/**
* An immutable implementation of {@link PublicKeyCredentialCreationOptionsRequest}.
*
* @author Rob Winch
* @since 6.4
*/
public class ImmutablePublicKeyCredentialCreationOptionsRequest implements PublicKeyCredentialCreationOptionsRequest {
private final Authentication authentication;
public ImmutablePublicKeyCredentialCreationOptionsRequest(Authentication authentication) {
Assert.notNull(authentication, "authentication cannot be null");
this.authentication = authentication;
}
@Override
public Authentication getAuthentication() {
return this.authentication;
}
}
@@ -1,34 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.management;
import org.springframework.security.core.Authentication;
public class ImmutablePublicKeyCredentialRequestOptionsRequest implements PublicKeyCredentialRequestOptionsRequest {
private final Authentication authentication;
public ImmutablePublicKeyCredentialRequestOptionsRequest(Authentication authentication) {
this.authentication = authentication;
}
@Override
public Authentication getAuthentication() {
return this.authentication;
}
}
@@ -1,60 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.management;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
import org.springframework.util.Assert;
/**
* Contains the information necessary to register a new Credential.
*
* @author Rob Winch
* @since 6.4
* @see Webauthn4JRelyingPartyOperations#registerCredential(RelyingPartyRegistrationRequest)
*/
public class ImmutableRelyingPartyRegistrationRequest implements RelyingPartyRegistrationRequest {
private final PublicKeyCredentialCreationOptions options;
private final RelyingPartyPublicKey publicKey;
/**
* Creates a new instance.
* @param options the {@link PublicKeyCredentialCreationOptions} that were saved when
* {@link WebAuthnRelyingPartyOperations#createCredentialRequestOptions(PublicKeyCredentialRequestOptionsRequest)}
* was called.
* @param publicKey this is submitted by the client and if validated stored.
*/
public ImmutableRelyingPartyRegistrationRequest(PublicKeyCredentialCreationOptions options,
RelyingPartyPublicKey publicKey) {
Assert.notNull(options, "options cannot be null");
Assert.notNull(publicKey, "publicKey cannot be null");
this.options = options;
this.publicKey = publicKey;
}
@Override
public PublicKeyCredentialCreationOptions getCreationOptions() {
return this.options;
}
@Override
public RelyingPartyPublicKey getPublicKey() {
return this.publicKey;
}
}
@@ -1,184 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.management;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.SqlParameterValue;
import org.springframework.security.web.webauthn.api.Bytes;
import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
import org.springframework.util.Assert;
/**
* A JDBC implementation of an {@link PublicKeyCredentialUserEntityRepository} that uses a
* {@link JdbcOperations} for {@link PublicKeyCredentialUserEntity} persistence.
*
* <b>NOTE:</b> This {@code PublicKeyCredentialUserEntityRepository} depends on the table
* definition described in
* "classpath:org/springframework/security/user-entities-schema.sql" and therefore MUST be
* defined in the database schema.
*
* @author Max Batischev
* @since 6.5
* @see PublicKeyCredentialUserEntityRepository
* @see PublicKeyCredentialUserEntity
* @see JdbcOperations
* @see RowMapper
*/
public final class JdbcPublicKeyCredentialUserEntityRepository implements PublicKeyCredentialUserEntityRepository {
private RowMapper<PublicKeyCredentialUserEntity> userEntityRowMapper = new UserEntityRecordRowMapper();
private Function<PublicKeyCredentialUserEntity, List<SqlParameterValue>> userEntityParametersMapper = new UserEntityParametersMapper();
private final JdbcOperations jdbcOperations;
private static final String TABLE_NAME = "user_entities";
// @formatter:off
private static final String COLUMN_NAMES = "id, "
+ "name, "
+ "display_name ";
// @formatter:on
// @formatter:off
private static final String SAVE_USER_SQL = "INSERT INTO " + TABLE_NAME
+ " (" + COLUMN_NAMES + ") VALUES (?, ?, ?)";
// @formatter:on
private static final String ID_FILTER = "id = ? ";
private static final String USER_NAME_FILTER = "name = ? ";
// @formatter:off
private static final String FIND_USER_BY_ID_SQL = "SELECT " + COLUMN_NAMES
+ " FROM " + TABLE_NAME
+ " WHERE " + ID_FILTER;
// @formatter:on
// @formatter:off
private static final String FIND_USER_BY_NAME_SQL = "SELECT " + COLUMN_NAMES
+ " FROM " + TABLE_NAME
+ " WHERE " + USER_NAME_FILTER;
// @formatter:on
private static final String DELETE_USER_SQL = "DELETE FROM " + TABLE_NAME + " WHERE " + ID_FILTER;
// @formatter:off
private static final String UPDATE_USER_SQL = "UPDATE " + TABLE_NAME
+ " SET name = ?, display_name = ? "
+ " WHERE " + ID_FILTER;
// @formatter:on
/**
* Constructs a {@code JdbcPublicKeyCredentialUserEntityRepository} using the provided
* parameters.
* @param jdbcOperations the JDBC operations
*/
public JdbcPublicKeyCredentialUserEntityRepository(JdbcOperations jdbcOperations) {
Assert.notNull(jdbcOperations, "jdbcOperations cannot be null");
this.jdbcOperations = jdbcOperations;
}
@Override
public PublicKeyCredentialUserEntity findById(Bytes id) {
Assert.notNull(id, "id cannot be null");
List<PublicKeyCredentialUserEntity> result = this.jdbcOperations.query(FIND_USER_BY_ID_SQL,
this.userEntityRowMapper, id.toBase64UrlString());
return !result.isEmpty() ? result.get(0) : null;
}
@Override
public PublicKeyCredentialUserEntity findByUsername(String username) {
Assert.hasText(username, "name cannot be null or empty");
List<PublicKeyCredentialUserEntity> result = this.jdbcOperations.query(FIND_USER_BY_NAME_SQL,
this.userEntityRowMapper, username);
return !result.isEmpty() ? result.get(0) : null;
}
@Override
public void save(PublicKeyCredentialUserEntity userEntity) {
Assert.notNull(userEntity, "userEntity cannot be null");
int rows = updateUserEntity(userEntity);
if (rows == 0) {
insertUserEntity(userEntity);
}
}
private void insertUserEntity(PublicKeyCredentialUserEntity userEntity) {
List<SqlParameterValue> parameters = this.userEntityParametersMapper.apply(userEntity);
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray());
this.jdbcOperations.update(SAVE_USER_SQL, pss);
}
private int updateUserEntity(PublicKeyCredentialUserEntity userEntity) {
List<SqlParameterValue> parameters = this.userEntityParametersMapper.apply(userEntity);
SqlParameterValue userEntityId = parameters.remove(0);
parameters.add(userEntityId);
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray());
return this.jdbcOperations.update(UPDATE_USER_SQL, pss);
}
@Override
public void delete(Bytes id) {
Assert.notNull(id, "id cannot be null");
SqlParameterValue[] parameters = new SqlParameterValue[] {
new SqlParameterValue(Types.VARCHAR, id.toBase64UrlString()), };
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters);
this.jdbcOperations.update(DELETE_USER_SQL, pss);
}
private static class UserEntityParametersMapper
implements Function<PublicKeyCredentialUserEntity, List<SqlParameterValue>> {
@Override
public List<SqlParameterValue> apply(PublicKeyCredentialUserEntity userEntity) {
List<SqlParameterValue> parameters = new ArrayList<>();
parameters.add(new SqlParameterValue(Types.VARCHAR, userEntity.getId().toBase64UrlString()));
parameters.add(new SqlParameterValue(Types.VARCHAR, userEntity.getName()));
parameters.add(new SqlParameterValue(Types.VARCHAR, userEntity.getDisplayName()));
return parameters;
}
}
private static class UserEntityRecordRowMapper implements RowMapper<PublicKeyCredentialUserEntity> {
@Override
public PublicKeyCredentialUserEntity mapRow(ResultSet rs, int rowNum) throws SQLException {
Bytes id = Bytes.fromBase64(new String(rs.getString("id").getBytes()));
String name = rs.getString("name");
String displayName = rs.getString("display_name");
return ImmutablePublicKeyCredentialUserEntity.builder().id(id).name(name).displayName(displayName).build();
}
}
}
@@ -1,357 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.management;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.SqlParameterValue;
import org.springframework.jdbc.support.lob.LobCreator;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.security.web.webauthn.api.AuthenticatorTransport;
import org.springframework.security.web.webauthn.api.Bytes;
import org.springframework.security.web.webauthn.api.CredentialRecord;
import org.springframework.security.web.webauthn.api.ImmutableCredentialRecord;
import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCose;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialType;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* A JDBC implementation of an {@link UserCredentialRepository} that uses a
* {@link JdbcOperations} for {@link CredentialRecord} persistence.
*
* <b>NOTE:</b> This {@code UserCredentialRepository} depends on the table definition
* described in "classpath:org/springframework/security/user-credentials-schema.sql" and
* therefore MUST be defined in the database schema.
*
* @author Max Batischev
* @since 6.5
* @see UserCredentialRepository
* @see CredentialRecord
* @see JdbcOperations
* @see RowMapper
*/
public final class JdbcUserCredentialRepository implements UserCredentialRepository {
private RowMapper<CredentialRecord> credentialRecordRowMapper = new CredentialRecordRowMapper(ResultSet::getBytes);
private Function<CredentialRecord, List<SqlParameterValue>> credentialRecordParametersMapper = new CredentialRecordParametersMapper();
private SetBytes setBytes = PreparedStatement::setBytes;
private final JdbcOperations jdbcOperations;
private static final String TABLE_NAME = "user_credentials";
// @formatter:off
private static final String COLUMN_NAMES = "credential_id, "
+ "user_entity_user_id, "
+ "public_key, "
+ "signature_count, "
+ "uv_initialized, "
+ "backup_eligible, "
+ "authenticator_transports, "
+ "public_key_credential_type, "
+ "backup_state, "
+ "attestation_object, "
+ "attestation_client_data_json, "
+ "created, "
+ "last_used, "
+ "label ";
// @formatter:on
// @formatter:off
private static final String SAVE_CREDENTIAL_RECORD_SQL = "INSERT INTO " + TABLE_NAME
+ " (" + COLUMN_NAMES + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
// @formatter:on
private static final String ID_FILTER = "credential_id = ? ";
private static final String USER_ID_FILTER = "user_entity_user_id = ? ";
// @formatter:off
private static final String FIND_CREDENTIAL_RECORD_BY_ID_SQL = "SELECT " + COLUMN_NAMES
+ " FROM " + TABLE_NAME
+ " WHERE " + ID_FILTER;
// @formatter:on
// @formatter:off
private static final String FIND_CREDENTIAL_RECORD_BY_USER_ID_SQL = "SELECT " + COLUMN_NAMES
+ " FROM " + TABLE_NAME
+ " WHERE " + USER_ID_FILTER;
// @formatter:on
private static final String DELETE_CREDENTIAL_RECORD_SQL = "DELETE FROM " + TABLE_NAME + " WHERE " + ID_FILTER;
// @formatter:off
private static final String UPDATE_CREDENTIAL_RECORD_SQL = "UPDATE " + TABLE_NAME
+ " SET user_entity_user_id = ?, " +
"public_key = ?, " +
"signature_count = ?, " +
"uv_initialized = ?, " +
"backup_eligible = ? ," +
"authenticator_transports = ?, " +
"public_key_credential_type = ?, " +
"backup_state = ?, " +
"attestation_object = ?, " +
"attestation_client_data_json = ?, " +
"created = ?, " +
"last_used = ?, " +
"label = ?"
+ " WHERE " + ID_FILTER;
// @formatter:on
/**
* Constructs a {@code JdbcUserCredentialRepository} using the provided parameters.
* @param jdbcOperations the JDBC operations
*/
public JdbcUserCredentialRepository(JdbcOperations jdbcOperations) {
Assert.notNull(jdbcOperations, "jdbcOperations cannot be null");
this.jdbcOperations = jdbcOperations;
}
@Override
public void delete(Bytes credentialId) {
Assert.notNull(credentialId, "credentialId cannot be null");
SqlParameterValue[] parameters = new SqlParameterValue[] {
new SqlParameterValue(Types.VARCHAR, credentialId.toBase64UrlString()), };
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters);
this.jdbcOperations.update(DELETE_CREDENTIAL_RECORD_SQL, pss);
}
@Override
public void save(CredentialRecord record) {
Assert.notNull(record, "record cannot be null");
int rows = updateCredentialRecord(record);
if (rows == 0) {
insertCredentialRecord(record);
}
}
private void insertCredentialRecord(CredentialRecord record) {
List<SqlParameterValue> parameters = this.credentialRecordParametersMapper.apply(record);
PreparedStatementSetter pss = new BlobArgumentPreparedStatementSetter(this.setBytes, parameters.toArray());
this.jdbcOperations.update(SAVE_CREDENTIAL_RECORD_SQL, pss);
}
private int updateCredentialRecord(CredentialRecord record) {
List<SqlParameterValue> parameters = this.credentialRecordParametersMapper.apply(record);
SqlParameterValue credentialId = parameters.remove(0);
parameters.add(credentialId);
PreparedStatementSetter pss = new BlobArgumentPreparedStatementSetter(this.setBytes, parameters.toArray());
return this.jdbcOperations.update(UPDATE_CREDENTIAL_RECORD_SQL, pss);
}
@Override
public CredentialRecord findByCredentialId(Bytes credentialId) {
Assert.notNull(credentialId, "credentialId cannot be null");
List<CredentialRecord> result = this.jdbcOperations.query(FIND_CREDENTIAL_RECORD_BY_ID_SQL,
this.credentialRecordRowMapper, credentialId.toBase64UrlString());
return !result.isEmpty() ? result.get(0) : null;
}
@Override
public List<CredentialRecord> findByUserId(Bytes userId) {
Assert.notNull(userId, "userId cannot be null");
return this.jdbcOperations.query(FIND_CREDENTIAL_RECORD_BY_USER_ID_SQL, this.credentialRecordRowMapper,
userId.toBase64UrlString());
}
/**
* Sets a {@link LobHandler} for large binary fields and large text field parameters.
* @param lobHandler the lob handler
* @deprecated {@link LobHandler} is deprecated without replacement, as such this
* method will also be removed without replacement
*/
@Deprecated(since = "6.5", forRemoval = true)
public void setLobHandler(LobHandler lobHandler) {
Assert.notNull(lobHandler, "lobHandler cannot be null");
this.setBytes = (ps, index, bytes) -> {
try (LobCreator creator = lobHandler.getLobCreator()) {
creator.setBlobAsBytes(ps, index, bytes);
}
};
this.credentialRecordRowMapper = new CredentialRecordRowMapper(lobHandler::getBlobAsBytes);
}
private static class CredentialRecordParametersMapper
implements Function<CredentialRecord, List<SqlParameterValue>> {
@Override
public List<SqlParameterValue> apply(CredentialRecord record) {
List<SqlParameterValue> parameters = new ArrayList<>();
List<String> transports = new ArrayList<>();
if (!CollectionUtils.isEmpty(record.getTransports())) {
for (AuthenticatorTransport transport : record.getTransports()) {
transports.add(transport.getValue());
}
}
parameters.add(new SqlParameterValue(Types.VARCHAR, record.getCredentialId().toBase64UrlString()));
parameters.add(new SqlParameterValue(Types.VARCHAR, record.getUserEntityUserId().toBase64UrlString()));
parameters.add(new SqlParameterValue(Types.BLOB, record.getPublicKey().getBytes()));
parameters.add(new SqlParameterValue(Types.BIGINT, record.getSignatureCount()));
parameters.add(new SqlParameterValue(Types.BOOLEAN, record.isUvInitialized()));
parameters.add(new SqlParameterValue(Types.BOOLEAN, record.isBackupEligible()));
parameters.add(new SqlParameterValue(Types.VARCHAR,
(!CollectionUtils.isEmpty(record.getTransports())) ? String.join(",", transports) : ""));
parameters.add(new SqlParameterValue(Types.VARCHAR,
(record.getCredentialType() != null) ? record.getCredentialType().getValue() : null));
parameters.add(new SqlParameterValue(Types.BOOLEAN, record.isBackupState()));
parameters.add(new SqlParameterValue(Types.BLOB,
(record.getAttestationObject() != null) ? record.getAttestationObject().getBytes() : null));
parameters.add(new SqlParameterValue(Types.BLOB, (record.getAttestationClientDataJSON() != null)
? record.getAttestationClientDataJSON().getBytes() : null));
parameters.add(new SqlParameterValue(Types.TIMESTAMP, fromInstant(record.getCreated())));
parameters.add(new SqlParameterValue(Types.TIMESTAMP, fromInstant(record.getLastUsed())));
parameters.add(new SqlParameterValue(Types.VARCHAR, record.getLabel()));
return parameters;
}
private Timestamp fromInstant(Instant instant) {
if (instant == null) {
return null;
}
return Timestamp.from(instant);
}
}
private interface SetBytes {
void setBytes(PreparedStatement ps, int index, byte[] bytes) throws SQLException;
}
private interface GetBytes {
byte[] getBytes(ResultSet rs, String columnName) throws SQLException;
}
private static final class BlobArgumentPreparedStatementSetter extends ArgumentPreparedStatementSetter {
private final SetBytes setBytes;
private BlobArgumentPreparedStatementSetter(SetBytes setBytes, Object[] args) {
super(args);
this.setBytes = setBytes;
}
@Override
protected void doSetValue(PreparedStatement ps, int parameterPosition, Object argValue) throws SQLException {
if (argValue instanceof SqlParameterValue paramValue) {
if (paramValue.getSqlType() == Types.BLOB) {
if (paramValue.getValue() != null) {
Assert.isInstanceOf(byte[].class, paramValue.getValue(),
"Value of blob parameter must be byte[]");
}
byte[] valueBytes = (byte[]) paramValue.getValue();
this.setBytes.setBytes(ps, parameterPosition, valueBytes);
return;
}
}
super.doSetValue(ps, parameterPosition, argValue);
}
}
private static class CredentialRecordRowMapper implements RowMapper<CredentialRecord> {
private final GetBytes getBytes;
CredentialRecordRowMapper(GetBytes getBytes) {
this.getBytes = getBytes;
}
@Override
public CredentialRecord mapRow(ResultSet rs, int rowNum) throws SQLException {
Bytes credentialId = Bytes.fromBase64(new String(rs.getString("credential_id").getBytes()));
Bytes userEntityUserId = Bytes.fromBase64(new String(rs.getString("user_entity_user_id").getBytes()));
ImmutablePublicKeyCose publicKey = new ImmutablePublicKeyCose(this.getBytes.getBytes(rs, "public_key"));
long signatureCount = rs.getLong("signature_count");
boolean uvInitialized = rs.getBoolean("uv_initialized");
boolean backupEligible = rs.getBoolean("backup_eligible");
PublicKeyCredentialType credentialType = PublicKeyCredentialType
.valueOf(rs.getString("public_key_credential_type"));
boolean backupState = rs.getBoolean("backup_state");
Bytes attestationObject = null;
byte[] rawAttestationObject = this.getBytes.getBytes(rs, "attestation_object");
if (rawAttestationObject != null) {
attestationObject = new Bytes(rawAttestationObject);
}
Bytes attestationClientDataJson = null;
byte[] rawAttestationClientDataJson = this.getBytes.getBytes(rs, "attestation_client_data_json");
if (rawAttestationClientDataJson != null) {
attestationClientDataJson = new Bytes(rawAttestationClientDataJson);
}
Instant created = fromTimestamp(rs.getTimestamp("created"));
Instant lastUsed = fromTimestamp(rs.getTimestamp("last_used"));
String label = rs.getString("label");
String[] transports = rs.getString("authenticator_transports").split(",");
Set<AuthenticatorTransport> authenticatorTransports = new HashSet<>();
for (String transport : transports) {
authenticatorTransports.add(AuthenticatorTransport.valueOf(transport));
}
return ImmutableCredentialRecord.builder()
.credentialId(credentialId)
.userEntityUserId(userEntityUserId)
.publicKey(publicKey)
.signatureCount(signatureCount)
.uvInitialized(uvInitialized)
.backupEligible(backupEligible)
.credentialType(credentialType)
.backupState(backupState)
.attestationObject(attestationObject)
.attestationClientDataJSON(attestationClientDataJson)
.created(created)
.label(label)
.lastUsed(lastUsed)
.transports(authenticatorTransports)
.build();
}
private Instant fromTimestamp(Timestamp timestamp) {
if (timestamp == null) {
return null;
}
return timestamp.toInstant();
}
}
}
@@ -1,67 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.management;
import java.util.HashMap;
import java.util.Map;
import org.springframework.security.web.webauthn.api.Bytes;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
import org.springframework.util.Assert;
/**
* A {@link Map} based implementation of {@link PublicKeyCredentialUserEntityRepository}.
*
* @author Rob Winch
* @since 6.4
*/
public class MapPublicKeyCredentialUserEntityRepository implements PublicKeyCredentialUserEntityRepository {
private final Map<String, PublicKeyCredentialUserEntity> usernameToUserEntity = new HashMap<>();
private final Map<Bytes, PublicKeyCredentialUserEntity> idToUserEntity = new HashMap<>();
@Override
public PublicKeyCredentialUserEntity findById(Bytes id) {
Assert.notNull(id, "id cannot be null");
return this.idToUserEntity.get(id);
}
@Override
public PublicKeyCredentialUserEntity findByUsername(String username) {
Assert.notNull(username, "username cannot be null");
return this.usernameToUserEntity.get(username);
}
@Override
public void save(PublicKeyCredentialUserEntity userEntity) {
if (userEntity == null) {
throw new IllegalArgumentException("userEntity cannot be null");
}
this.usernameToUserEntity.put(userEntity.getName(), userEntity);
this.idToUserEntity.put(userEntity.getId(), userEntity);
}
@Override
public void delete(Bytes id) {
PublicKeyCredentialUserEntity existing = this.idToUserEntity.remove(id);
if (existing != null) {
this.usernameToUserEntity.remove(existing.getName());
}
}
}
@@ -1,77 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.management;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.security.web.webauthn.api.Bytes;
import org.springframework.security.web.webauthn.api.CredentialRecord;
import org.springframework.util.Assert;
/**
* A {@link Map} based implementation of {@link UserCredentialRepository}.
*
* @author Rob Winch
* @since 6.4
*/
public class MapUserCredentialRepository implements UserCredentialRepository {
private final Map<Bytes, CredentialRecord> credentialIdToUserCredential = new HashMap<>();
private final Map<Bytes, Set<Bytes>> userEntityIdToUserCredentialIds = new HashMap<>();
@Override
public void delete(Bytes credentialId) {
Assert.notNull(credentialId, "credentialId cannot be null");
CredentialRecord credentialRecord = this.credentialIdToUserCredential.remove(credentialId);
if (credentialRecord != null) {
Set<Bytes> credentialIds = this.userEntityIdToUserCredentialIds.get(credentialRecord.getUserEntityUserId());
if (credentialIds != null) {
credentialIds.remove(credentialId);
}
}
}
@Override
public void save(CredentialRecord credentialRecord) {
Assert.notNull(credentialRecord, "credentialRecord cannot be null");
this.credentialIdToUserCredential.put(credentialRecord.getCredentialId(), credentialRecord);
this.userEntityIdToUserCredentialIds
.computeIfAbsent(credentialRecord.getUserEntityUserId(), (id) -> new HashSet<>())
.add(credentialRecord.getCredentialId());
}
@Override
public CredentialRecord findByCredentialId(Bytes credentialId) {
Assert.notNull(credentialId, "credentialId cannot be null");
return this.credentialIdToUserCredential.get(credentialId);
}
@Override
public List<CredentialRecord> findByUserId(Bytes userId) {
Assert.notNull(userId, "userId cannot be null");
Set<Bytes> credentialIds = this.userEntityIdToUserCredentialIds.getOrDefault(userId, Collections.emptySet());
return credentialIds.stream().map(this::findByCredentialId).collect(Collectors.toUnmodifiableList());
}
}
@@ -1,38 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.management;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
/**
* A request to create a new {@link PublicKeyCredentialCreationOptions}.
*
* @author Rob Winch
* @since 6.4
* @see WebAuthnRelyingPartyOperations#createPublicKeyCredentialCreationOptions(PublicKeyCredentialCreationOptionsRequest)
*/
public interface PublicKeyCredentialCreationOptionsRequest {
/**
* The current {@link Authentication}. It must be authenticated to associate the
* credential to a user.
* @return the {@link Authentication} to use.
*/
Authentication getAuthentication();
}
@@ -1,29 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.management;
import org.springframework.security.core.Authentication;
public interface PublicKeyCredentialRequestOptionsRequest {
/**
* The current {@link Authentication}. Possibly null or an anonymous.
* @return the current {@link Authentication}
*/
Authentication getAuthentication();
}
@@ -1,54 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.management;
import org.springframework.security.web.webauthn.api.Bytes;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
/**
* A repository for managing {@link PublicKeyCredentialUserEntity} instances.
*
* @author Rob Winch
* @since 6.4
*/
public interface PublicKeyCredentialUserEntityRepository {
/**
* Finds the {@link PublicKeyCredentialUserEntity} by
* {@link PublicKeyCredentialUserEntity#getId()}
* @param id the id to lookup the username by
* @return the username or null if not found.
*/
PublicKeyCredentialUserEntity findById(Bytes id);
/**
* Finds the {@link PublicKeyCredentialUserEntity} by the username.
* @param username the username to lookup the {@link PublicKeyCredentialUserEntity}
* @return the {@link PublicKeyCredentialUserEntity} or null if not found.
*/
PublicKeyCredentialUserEntity findByUsername(String username);
/**
* Saves the {@link PublicKeyCredentialUserEntity} to the associated username.
* @param userEntity the {@link PublicKeyCredentialUserEntity} to associate to the
* provided username. If null, any existing entry is deleted.
*/
void save(PublicKeyCredentialUserEntity userEntity);
void delete(Bytes id);
}
@@ -1,73 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.management;
import java.io.Serial;
import java.io.Serializable;
import org.springframework.security.web.webauthn.api.AuthenticatorAssertionResponse;
import org.springframework.security.web.webauthn.api.PublicKeyCredential;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions;
import org.springframework.util.Assert;
/**
* The data object used to provide the information necessary to authenticate a user with
* WebAuthn.
*
* @author Rob Winch
* @since 6.4
* @see WebAuthnRelyingPartyOperations#authenticate(RelyingPartyAuthenticationRequest)
*/
public class RelyingPartyAuthenticationRequest implements Serializable {
@Serial
private static final long serialVersionUID = -928083091875202086L;
private final PublicKeyCredentialRequestOptions requestOptions;
private final PublicKeyCredential<AuthenticatorAssertionResponse> publicKey;
/**
* Creates a new instance.
* @param requestOptions the {@link PublicKeyCredentialRequestOptions}
* @param publicKey the {@link PublicKeyCredential}
*/
public RelyingPartyAuthenticationRequest(PublicKeyCredentialRequestOptions requestOptions,
PublicKeyCredential<AuthenticatorAssertionResponse> publicKey) {
Assert.notNull(requestOptions, "requestOptions cannot be null");
Assert.notNull(publicKey, "publicKey cannot be null");
this.requestOptions = requestOptions;
this.publicKey = publicKey;
}
/**
* Ges the request options.
* @return the request options.
*/
public PublicKeyCredentialRequestOptions getRequestOptions() {
return this.requestOptions;
}
/**
* Gets the public key.
* @return the public key.
*/
public PublicKeyCredential<AuthenticatorAssertionResponse> getPublicKey() {
return this.publicKey;
}
}
@@ -1,55 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.management;
import org.springframework.security.web.webauthn.api.AuthenticatorAttestationResponse;
import org.springframework.security.web.webauthn.api.PublicKeyCredential;
import org.springframework.util.Assert;
/**
* Submitted by a client to request registration of a new credential.
*
* @author Rob Winch
* @since 6.4
*/
public class RelyingPartyPublicKey {
private final PublicKeyCredential<AuthenticatorAttestationResponse> credential;
private final String label;
/**
* Creates a new instance.
* @param credential the credential
* @param label a human readable label that will be associated to the credential
*/
public RelyingPartyPublicKey(PublicKeyCredential<AuthenticatorAttestationResponse> credential, String label) {
Assert.notNull(credential, "credential cannot be null");
Assert.notNull(label, "label cannot be null");
this.credential = credential;
this.label = label;
}
public PublicKeyCredential<AuthenticatorAttestationResponse> getCredential() {
return this.credential;
}
public String getLabel() {
return this.label;
}
}
@@ -1,32 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.management;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
/**
* @author Rob Winch
* @since 6.4
* @see WebAuthnRelyingPartyOperations#registerCredential(RelyingPartyRegistrationRequest)
*/
public interface RelyingPartyRegistrationRequest {
PublicKeyCredentialCreationOptions getCreationOptions();
RelyingPartyPublicKey getPublicKey();
}
@@ -1,62 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.management;
import java.util.List;
import org.springframework.security.web.webauthn.api.Bytes;
import org.springframework.security.web.webauthn.api.CredentialRecord;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
/**
* A repository for managing {@link CredentialRecord}s associated to a user.
*
* @author Rob Winch
* @since 6.4
*/
public interface UserCredentialRepository {
/**
* Deletes an entry by credential id
* @param credentialId {@link CredentialRecord#getCredentialId()}
*/
void delete(Bytes credentialId);
/**
* Saves a {@link CredentialRecord}
* @param credentialRecord the {@link CredentialRecord} to save.
*/
void save(CredentialRecord credentialRecord);
/**
* Finds an entry by credential id.
* @param credentialId {@link CredentialRecord#getCredentialId()}
* @return the {@link CredentialRecord} or null if not found.
*/
CredentialRecord findByCredentialId(Bytes credentialId);
/**
* Finds all {@link CredentialRecord} instances for a specific user.
* @param userId the {@link PublicKeyCredentialUserEntity#getId()} to search for a
* user.
* @return all {@link CredentialRecord} instances for a specific user or empty if no
* results found. Never null.
* @see PublicKeyCredentialUserEntityRepository
*/
List<CredentialRecord> findByUserId(Bytes userId);
}
@@ -1,72 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.management;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.webauthn.api.CredentialRecord;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
/**
* An API for <a href="https://www.w3.org/TR/webauthn-3/#sctn-rp-operations">WebAuthn
* Relying Party Operations</a>
*
* @author Rob Winch
* @since 6.4
*/
public interface WebAuthnRelyingPartyOperations {
/**
* Creates the {@link PublicKeyCredentialCreationOptions} used to register new
* credentials.
* @param request the {@link PublicKeyCredentialCreationOptionsRequest} to create the
* {@link PublicKeyCredentialCreationOptions}
* @return the {@link PublicKeyCredentialCreationOptions} for the
* {@link Authentication} passed in. Cannot be null.
*/
PublicKeyCredentialCreationOptions createPublicKeyCredentialCreationOptions(
PublicKeyCredentialCreationOptionsRequest request);
/**
* If {@link RelyingPartyRegistrationRequest} is valid, will <a href=
* "https://www.w3.org/TR/webauthn-3/#sctn-registering-a-new-credential">register</a>
* and return a new {@link CredentialRecord}.
* @param relyingPartyRegistrationRequest the {@link RelyingPartyRegistrationRequest}
* to process.
* @return a new {@link CredentialRecord}
* @throws RuntimeException if the {@link RelyingPartyRegistrationRequest} is not
* valid.
*/
CredentialRecord registerCredential(RelyingPartyRegistrationRequest relyingPartyRegistrationRequest);
/**
* Creates the {@link PublicKeyCredentialRequestOptions} used to authenticate a user.
* @param request the {@link PublicKeyCredentialRequestOptionsRequest}.
* @return the {@link PublicKeyCredentialRequestOptions} used to authenticate a user.
*/
PublicKeyCredentialRequestOptions createCredentialRequestOptions(PublicKeyCredentialRequestOptionsRequest request);
/**
* Authenticates the {@link RelyingPartyAuthenticationRequest} passed in
* @param request the {@link RelyingPartyAuthenticationRequest}
* @return the principal name (e.g. username) if authentication was successful
* @throws RuntimeException if authentication fails
*/
PublicKeyCredentialUserEntity authenticate(RelyingPartyAuthenticationRequest request);
}
@@ -1,403 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.management;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import com.webauthn4j.WebAuthnManager;
import com.webauthn4j.authenticator.Authenticator;
import com.webauthn4j.authenticator.AuthenticatorImpl;
import com.webauthn4j.converter.util.CborConverter;
import com.webauthn4j.converter.util.ObjectConverter;
import com.webauthn4j.data.AuthenticationData;
import com.webauthn4j.data.AuthenticationParameters;
import com.webauthn4j.data.RegistrationData;
import com.webauthn4j.data.RegistrationParameters;
import com.webauthn4j.data.RegistrationRequest;
import com.webauthn4j.data.attestation.AttestationObject;
import com.webauthn4j.data.attestation.authenticator.AttestedCredentialData;
import com.webauthn4j.data.attestation.authenticator.AuthenticatorData;
import com.webauthn4j.data.attestation.authenticator.COSEKey;
import com.webauthn4j.data.client.Origin;
import com.webauthn4j.data.client.challenge.Challenge;
import com.webauthn4j.data.client.challenge.DefaultChallenge;
import com.webauthn4j.data.extension.authenticator.RegistrationExtensionAuthenticatorOutput;
import com.webauthn4j.server.ServerProperty;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.webauthn.api.AttestationConveyancePreference;
import org.springframework.security.web.webauthn.api.AuthenticatorAssertionResponse;
import org.springframework.security.web.webauthn.api.AuthenticatorAttestationResponse;
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.CredentialRecord;
import org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInput;
import org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInputs;
import org.springframework.security.web.webauthn.api.ImmutableCredentialRecord;
import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCose;
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.PublicKeyCredentialCreationOptions.PublicKeyCredentialCreationOptionsBuilder;
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.PublicKeyCredentialRequestOptions.PublicKeyCredentialRequestOptionsBuilder;
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.UserVerificationRequirement;
import org.springframework.util.Assert;
/**
* A <a href="https://webauthn4j.github.io/webauthn4j/en/">WebAuthn4j</a> implementation
* of {@link WebAuthnRelyingPartyOperations}.
*
* @author Rob Winch
* @since 6.4
*/
public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOperations {
private final PublicKeyCredentialUserEntityRepository userEntities;
private final UserCredentialRepository userCredentials;
private final Set<String> allowedOrigins;
private final PublicKeyCredentialRpEntity rp;
private final ObjectConverter objectConverter = new ObjectConverter();
private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
private WebAuthnManager webAuthnManager = WebAuthnManager.createNonStrictWebAuthnManager();
private Consumer<PublicKeyCredentialCreationOptionsBuilder> customizeCreationOptions = (options) -> {
};
private Consumer<PublicKeyCredentialRequestOptionsBuilder> customizeRequestOptions = (options) -> {
};
/**
* Creates a new instance.
* @param userEntities the {@link PublicKeyCredentialUserEntityRepository} to use.
* @param userCredentials the {@link UserCredentialRepository} to use.
* @param rpEntity the {@link PublicKeyCredentialRpEntity} to use.
* @param allowedOrigins the allowed origins.
*/
public Webauthn4JRelyingPartyOperations(PublicKeyCredentialUserEntityRepository userEntities,
UserCredentialRepository userCredentials, PublicKeyCredentialRpEntity rpEntity,
Set<String> allowedOrigins) {
Assert.notNull(userEntities, "userEntities cannot be null");
Assert.notNull(userCredentials, "userCredentials cannot be null");
Assert.notNull(rpEntity, "rpEntity cannot be null");
Assert.notNull(allowedOrigins, "allowedOrigins cannot be null");
this.userEntities = userEntities;
this.userCredentials = userCredentials;
this.rp = rpEntity;
this.allowedOrigins = allowedOrigins;
}
/**
* Sets the {@link WebAuthnManager} to use. The default is
* {@link WebAuthnManager#createNonStrictWebAuthnManager()}
* @param webAuthnManager the {@link WebAuthnManager}.
*/
public void setWebAuthnManager(WebAuthnManager webAuthnManager) {
Assert.notNull(webAuthnManager, "webAuthnManager cannot be null");
this.webAuthnManager = webAuthnManager;
}
/**
* Sets a {@link Consumer} used to customize the
* {@link PublicKeyCredentialCreationOptionsBuilder} for
* {@link #createPublicKeyCredentialCreationOptions(PublicKeyCredentialCreationOptionsRequest)}.
* The default values are always populated, but can be overridden with this property.
* @param customizeCreationOptions the {@link Consumer} to customize the
* {@link PublicKeyCredentialCreationOptionsBuilder}
*/
public void setCustomizeCreationOptions(
Consumer<PublicKeyCredentialCreationOptionsBuilder> customizeCreationOptions) {
Assert.notNull(customizeCreationOptions, "customizeCreationOptions must not be null");
this.customizeCreationOptions = customizeCreationOptions;
}
/**
* Sets a {@link Consumer} used to customize the
* {@link PublicKeyCredentialRequestOptionsBuilder} for
* {@link #createCredentialRequestOptions(PublicKeyCredentialRequestOptionsRequest)}.The
* default values are always populated, but can be overridden with this property.
* @param customizeRequestOptions the {@link Consumer} to customize the
* {@link PublicKeyCredentialRequestOptionsBuilder}
*/
public void setCustomizeRequestOptions(Consumer<PublicKeyCredentialRequestOptionsBuilder> customizeRequestOptions) {
Assert.notNull(customizeRequestOptions, "customizeRequestOptions cannot be null");
this.customizeRequestOptions = customizeRequestOptions;
}
@Override
public PublicKeyCredentialCreationOptions createPublicKeyCredentialCreationOptions(
PublicKeyCredentialCreationOptionsRequest request) {
if (request == null) {
throw new IllegalArgumentException("request cannot be null");
}
Authentication authentication = request.getAuthentication();
if (!this.trustResolver.isAuthenticated(authentication)) {
throw new IllegalArgumentException("Authentication must be authenticated");
}
AuthenticatorSelectionCriteria authenticatorSelection = AuthenticatorSelectionCriteria.builder()
.userVerification(UserVerificationRequirement.PREFERRED)
.residentKey(ResidentKeyRequirement.REQUIRED)
.build();
ImmutableAuthenticationExtensionsClientInputs clientInputs = new ImmutableAuthenticationExtensionsClientInputs(
ImmutableAuthenticationExtensionsClientInput.credProps);
PublicKeyCredentialUserEntity userEntity = findUserEntityOrCreateAndSave(authentication.getName());
List<CredentialRecord> credentialRecords = this.userCredentials.findByUserId(userEntity.getId());
PublicKeyCredentialCreationOptions options = PublicKeyCredentialCreationOptions.builder()
.attestation(AttestationConveyancePreference.NONE)
.pubKeyCredParams(PublicKeyCredentialParameters.EdDSA, PublicKeyCredentialParameters.ES256,
PublicKeyCredentialParameters.RS256)
.authenticatorSelection(authenticatorSelection)
.challenge(Bytes.random())
.extensions(clientInputs)
.timeout(Duration.ofMinutes(5))
.user(userEntity)
.rp(this.rp)
.excludeCredentials(credentialDescriptors(credentialRecords))
.customize(this.customizeCreationOptions)
.build();
return options;
}
private static List<PublicKeyCredentialDescriptor> credentialDescriptors(List<CredentialRecord> credentialRecords) {
List<PublicKeyCredentialDescriptor> result = new ArrayList<>();
for (CredentialRecord credentialRecord : credentialRecords) {
Bytes id = Bytes.fromBase64(credentialRecord.getCredentialId().toBase64UrlString());
PublicKeyCredentialDescriptor credentialDescriptor = PublicKeyCredentialDescriptor.builder()
.id(id)
.transports(credentialRecord.getTransports())
.build();
result.add(credentialDescriptor);
}
return result;
}
private PublicKeyCredentialUserEntity findUserEntityOrCreateAndSave(String username) {
final PublicKeyCredentialUserEntity foundUserEntity = this.userEntities.findByUsername(username);
if (foundUserEntity != null) {
return foundUserEntity;
}
PublicKeyCredentialUserEntity userEntity = ImmutablePublicKeyCredentialUserEntity.builder()
.displayName(username)
.id(Bytes.random())
.name(username)
.build();
this.userEntities.save(userEntity);
return userEntity;
}
@Override
public CredentialRecord registerCredential(RelyingPartyRegistrationRequest rpRegistrationRequest) {
Assert.notNull(rpRegistrationRequest, "rpRegistrationRequest cannot be null");
Bytes credentialId = rpRegistrationRequest.getPublicKey().getCredential().getRawId();
CredentialRecord existingCredential = this.userCredentials.findByCredentialId(credentialId);
if (existingCredential != null) {
throw new IllegalArgumentException("Credential with id " + credentialId + " already exists");
}
PublicKeyCredentialCreationOptions creationOptions = rpRegistrationRequest.getCreationOptions();
String rpId = creationOptions.getRp().getId();
RelyingPartyPublicKey publicKey = rpRegistrationRequest.getPublicKey();
PublicKeyCredential<AuthenticatorAttestationResponse> credential = publicKey.getCredential();
AuthenticatorAttestationResponse response = credential.getResponse();
// Server properties
Set<Origin> origins = toOrigins();
byte[] base64Challenge = creationOptions.getChallenge().getBytes();
byte[] attestationObject = response.getAttestationObject().getBytes();
byte[] clientDataJSON = response.getClientDataJSON().getBytes();
Challenge challenge = new DefaultChallenge(base64Challenge);
byte[] tokenBindingId = null /* set tokenBindingId */; // FIXME:
// https://www.w3.org/TR/webauthn-1/#dom-collectedclientdata-tokenbinding
ServerProperty serverProperty = new ServerProperty(origins, rpId, challenge, tokenBindingId);
boolean userVerificationRequired = creationOptions.getAuthenticatorSelection()
.getUserVerification() == UserVerificationRequirement.REQUIRED;
// requireUserPresence The constant Boolean value true
// https://www.w3.org/TR/webauthn-3/#sctn-op-make-cred
boolean userPresenceRequired = true;
List<com.webauthn4j.data.PublicKeyCredentialParameters> pubKeyCredParams = convertCredentialParamsToWebauthn4j(
creationOptions.getPubKeyCredParams());
Set<String> transports = convertTransportsToString(response);
RegistrationRequest webauthn4jRegistrationRequest = new RegistrationRequest(attestationObject, clientDataJSON,
transports);
RegistrationParameters registrationParameters = new RegistrationParameters(serverProperty, pubKeyCredParams,
userVerificationRequired, userPresenceRequired);
RegistrationData registrationData = this.webAuthnManager.validate(webauthn4jRegistrationRequest,
registrationParameters);
AuthenticatorData<RegistrationExtensionAuthenticatorOutput> authData = registrationData.getAttestationObject()
.getAuthenticatorData();
CborConverter cborConverter = this.objectConverter.getCborConverter();
COSEKey coseKey = authData.getAttestedCredentialData().getCOSEKey();
byte[] rawCoseKey = cborConverter.writeValueAsBytes(coseKey);
ImmutableCredentialRecord userCredential = ImmutableCredentialRecord.builder()
.userEntityUserId(creationOptions.getUser().getId())
.credentialType(credential.getType())
.credentialId(credential.getRawId())
.publicKey(new ImmutablePublicKeyCose(rawCoseKey))
.signatureCount(authData.getSignCount())
.uvInitialized(authData.isFlagUV())
.transports(convertTransports(registrationData.getTransports()))
.backupEligible(authData.isFlagBE())
.backupState(authData.isFlagBS())
.label(publicKey.getLabel())
.attestationClientDataJSON(credential.getResponse().getClientDataJSON())
.attestationObject(credential.getResponse().getAttestationObject())
.build();
this.userCredentials.save(userCredential);
return userCredential;
}
private static Set<String> convertTransportsToString(AuthenticatorAttestationResponse response) {
if (response.getTransports() == null) {
return null;
}
Set<String> transports = new HashSet<>(response.getTransports().size());
for (AuthenticatorTransport transport : response.getTransports()) {
transports.add(transport.getValue());
}
return transports;
}
private List<com.webauthn4j.data.PublicKeyCredentialParameters> convertCredentialParamsToWebauthn4j(
List<PublicKeyCredentialParameters> parameters) {
return parameters.stream().map(this::convertParamToWebauthn4j).collect(Collectors.toUnmodifiableList());
}
private com.webauthn4j.data.PublicKeyCredentialParameters convertParamToWebauthn4j(
PublicKeyCredentialParameters parameter) {
if (parameter.getType() != PublicKeyCredentialType.PUBLIC_KEY) {
throw new IllegalArgumentException(
"Cannot convert unknown credential type " + parameter.getType() + " to webauthn4j");
}
long algValue = parameter.getAlg().getValue();
com.webauthn4j.data.attestation.statement.COSEAlgorithmIdentifier alg = com.webauthn4j.data.attestation.statement.COSEAlgorithmIdentifier
.create(algValue);
return new com.webauthn4j.data.PublicKeyCredentialParameters(
com.webauthn4j.data.PublicKeyCredentialType.PUBLIC_KEY, alg);
}
private Set<Origin> toOrigins() {
return this.allowedOrigins.stream().map(Origin::new).collect(Collectors.toSet());
}
private static Set<AuthenticatorTransport> convertTransports(
Set<com.webauthn4j.data.AuthenticatorTransport> transports) {
if (transports == null) {
return Collections.emptySet();
}
return transports.stream()
.map((t) -> AuthenticatorTransport.valueOf(t.getValue()))
.collect(Collectors.toUnmodifiableSet());
}
@Override
public PublicKeyCredentialRequestOptions createCredentialRequestOptions(
PublicKeyCredentialRequestOptionsRequest request) {
Authentication authentication = request.getAuthentication();
List<CredentialRecord> credentialRecords = findCredentialRecords(authentication);
return PublicKeyCredentialRequestOptions.builder()
.allowCredentials(credentialDescriptors(credentialRecords))
.challenge(Bytes.random())
.rpId(this.rp.getId())
.timeout(Duration.ofMinutes(5))
.userVerification(UserVerificationRequirement.PREFERRED)
.customize(this.customizeRequestOptions)
.build();
}
private List<CredentialRecord> findCredentialRecords(Authentication authentication) {
if (!this.trustResolver.isAuthenticated(authentication)) {
return Collections.emptyList();
}
PublicKeyCredentialUserEntity userEntity = this.userEntities.findByUsername(authentication.getName());
if (userEntity == null) {
return Collections.emptyList();
}
return this.userCredentials.findByUserId(userEntity.getId());
}
@Override
public PublicKeyCredentialUserEntity authenticate(RelyingPartyAuthenticationRequest request) {
PublicKeyCredentialRequestOptions requestOptions = request.getRequestOptions();
AuthenticatorAssertionResponse assertionResponse = request.getPublicKey().getResponse();
Bytes keyId = request.getPublicKey().getRawId();
CredentialRecord credentialRecord = this.userCredentials.findByCredentialId(keyId);
CborConverter cborConverter = this.objectConverter.getCborConverter();
AttestationObject attestationObject = cborConverter
.readValue(credentialRecord.getAttestationObject().getBytes(), AttestationObject.class);
AuthenticatorData<RegistrationExtensionAuthenticatorOutput> authData = attestationObject.getAuthenticatorData();
AttestedCredentialData data = new AttestedCredentialData(authData.getAttestedCredentialData().getAaguid(),
keyId.getBytes(), authData.getAttestedCredentialData().getCOSEKey());
Authenticator authenticator = new AuthenticatorImpl(data, attestationObject.getAttestationStatement(),
credentialRecord.getSignatureCount());
Set<Origin> origins = toOrigins();
Challenge challenge = new DefaultChallenge(requestOptions.getChallenge().getBytes());
// FIXME: should populate this
byte[] tokenBindingId = null /* set tokenBindingId */;
ServerProperty serverProperty = new ServerProperty(origins, requestOptions.getRpId(), challenge,
tokenBindingId);
boolean userVerificationRequired = request.getRequestOptions()
.getUserVerification() == UserVerificationRequirement.REQUIRED;
com.webauthn4j.data.AuthenticationRequest authenticationRequest = new com.webauthn4j.data.AuthenticationRequest(
request.getPublicKey().getId().getBytes(), assertionResponse.getAuthenticatorData().getBytes(),
assertionResponse.getClientDataJSON().getBytes(), assertionResponse.getSignature().getBytes());
AuthenticationParameters authenticationParameters = new AuthenticationParameters(serverProperty, authenticator,
userVerificationRequired);
AuthenticationData authenticationData = this.webAuthnManager.validate(authenticationRequest,
authenticationParameters);
long updatedSignCount = authenticationData.getAuthenticatorData().getSignCount();
ImmutableCredentialRecord updatedRecord = ImmutableCredentialRecord.fromCredentialRecord(credentialRecord)
.lastUsed(Instant.now())
.signatureCount(updatedSignCount)
.build();
this.userCredentials.save(updatedRecord);
return this.userEntities.findById(credentialRecord.getUserEntityUserId());
}
}
@@ -1,218 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.registration;
import java.io.IOException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.security.web.webauthn.api.CredentialRecord;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
import org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository;
import org.springframework.security.web.webauthn.management.UserCredentialRepository;
import org.springframework.util.Assert;
import org.springframework.web.filter.OncePerRequestFilter;
/**
* A {@link jakarta.servlet.Filter} that renders a default WebAuthn registration page.
*
* @author Rob Winch
* @author Daniel Garnier-Moiroux
* @since 6.4
*/
public class DefaultWebAuthnRegistrationPageGeneratingFilter extends OncePerRequestFilter {
private RequestMatcher matcher = PathPatternRequestMatcher.withDefaults()
.matcher(HttpMethod.GET, "/webauthn/register");
private final PublicKeyCredentialUserEntityRepository userEntities;
private final UserCredentialRepository userCredentials;
/**
* Creates a new instance.
* @param userEntities the {@link PublicKeyCredentialUserEntity}
* @param userCredentials
*/
public DefaultWebAuthnRegistrationPageGeneratingFilter(PublicKeyCredentialUserEntityRepository userEntities,
UserCredentialRepository userCredentials) {
Assert.notNull(userEntities, "userEntities cannot be null");
Assert.notNull(userCredentials, "userCredentials cannot be null");
this.userEntities = userEntities;
this.userCredentials = userCredentials;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (!this.matcher.matches(request)) {
filterChain.doFilter(request, response);
return;
}
CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
response.setContentType(MediaType.TEXT_HTML_VALUE);
response.setStatus(HttpServletResponse.SC_OK);
String processedTemplate = HtmlTemplates.fromTemplate(HTML_TEMPLATE)
.withValue("contextPath", request.getContextPath())
.withRawHtml("csrfHeaders", renderCsrfHeader(csrfToken))
.withRawHtml("passkeys", passkeyRows(request.getRemoteUser(), request.getContextPath(), csrfToken))
.render();
response.getWriter().write(processedTemplate);
}
private String passkeyRows(String username, String contextPath, CsrfToken csrfToken) {
PublicKeyCredentialUserEntity userEntity = this.userEntities.findByUsername(username);
List<CredentialRecord> credentials = (userEntity != null)
? this.userCredentials.findByUserId(userEntity.getId()) : Collections.emptyList();
if (credentials.isEmpty()) {
return """
<tr><td colspan="5">No Passkeys</td></tr>
""";
}
return credentials.stream()
.map((credentialRecord) -> renderPasskeyRow(credentialRecord, contextPath, csrfToken))
.collect(Collectors.joining("\n"));
}
private String renderPasskeyRow(CredentialRecord credential, String contextPath, CsrfToken csrfToken) {
return HtmlTemplates.fromTemplate(PASSKEY_ROW_TEMPLATE)
.withValue("label", credential.getLabel())
.withValue("created", formatInstant(credential.getCreated()))
.withValue("lastUsed", formatInstant(credential.getLastUsed()))
.withValue("signatureCount", credential.getSignatureCount())
.withValue("credentialId", credential.getCredentialId().toBase64UrlString())
.withValue("csrfParameterName", csrfToken.getParameterName())
.withValue("csrfToken", csrfToken.getToken())
.withValue("contextPath", contextPath)
.render();
}
private static String formatInstant(Instant created) {
return ZonedDateTime.ofInstant(created, ZoneId.of("UTC"))
.truncatedTo(ChronoUnit.SECONDS)
.format(DateTimeFormatter.ISO_INSTANT);
}
private String renderCsrfHeader(CsrfToken csrfToken) {
return HtmlTemplates.fromTemplate(CSRF_HEADERS)
.withValue("headerName", csrfToken.getHeaderName())
.withValue("headerValue", csrfToken.getToken())
.render();
}
private static final String HTML_TEMPLATE = """
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>WebAuthn Registration</title>
<link href="{{contextPath}}/default-ui.css" rel="stylesheet" />
<script type="text/javascript" src="{{contextPath}}/login/webauthn.js"></script>
<script type="text/javascript">
<!--
const ui = {
getRegisterButton: function() {
return document.getElementById('register')
},
getSuccess: function() {
return document.getElementById('success')
},
getError: function() {
return document.getElementById('error')
},
getLabelInput: function() {
return document.getElementById('label')
},
getDeleteForms: function() {
return Array.from(document.getElementsByClassName("delete-form"))
},
}
document.addEventListener("DOMContentLoaded",() => setupRegistration({{csrfHeaders}}, "{{contextPath}}", ui));
//-->
</script>
</head>
<body>
<div class="content">
<h2 class="center">WebAuthn Registration</h2>
<form class="default-form" method="post" action="#" onclick="return false">
<div id="success" class="alert alert-success" role="alert">Success!</div>
<div id="error" class="alert alert-danger" role="alert"></div>
<p>
<label for="label" class="screenreader">Passkey Label</label>
<input type="text" id="label" name="label" placeholder="Passkey Label" required autofocus>
</p>
<button id="register" class="primary" type="submit">Register</button>
</form>
<table class="table table-striped">
<thead>
<tr class="table-header">
<th>Label</th>
<th>Created</th>
<th>Last Used</th>
<th>Signature Count</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
{{passkeys}}
</tbody>
</table>
</div>
</body>
</html>
""";
private static final String PASSKEY_ROW_TEMPLATE = """
<tr class="v-middle">
<td>{{label}}</td>
<td>{{created}}</td>
<td>{{lastUsed}}</td>
<td class="center">{{signatureCount}}</td>
<td>
<form class="delete-form no-margin" method="post" action="{{contextPath}}/webauthn/register/{{credentialId}}">
<input type="hidden" name="method" value="delete">
<input type="hidden" name="{{csrfParameterName}}" value="{{csrfToken}}">
<button class="primary small" type="submit">Delete</button>
</form>
</td>
</tr>
""";
private static final String CSRF_HEADERS = """
{"{{headerName}}" : "{{headerValue}}"}""";
}
@@ -1,110 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.registration;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.util.HtmlUtils;
/**
* Render HTML templates using string substitution. Intended for internal use. Variables
* can be templated using double curly-braces: {@code {{name}}}.
*
* @author Daniel Garnier-Moiroux
* @since 6.4
* @see org.springframework.security.web.authentication.ui.HtmlTemplates
*/
final class HtmlTemplates {
private HtmlTemplates() {
}
static HtmlTemplates.Builder fromTemplate(String template) {
return new HtmlTemplates.Builder(template);
}
static final class Builder {
private final String template;
private final Map<String, String> values = new HashMap<>();
private Builder(String template) {
this.template = template;
}
/**
* HTML-escape, and inject value {@code value} in every {@code {{key}}}
* placeholder.
* @param key the placeholder name
* @param value the value to inject
* @return this instance for further templating
*/
HtmlTemplates.Builder withValue(String key, Object value) {
Assert.notNull(value, "value cannot be null");
this.values.put(key, HtmlUtils.htmlEscape(value.toString()));
return this;
}
/**
* Inject value {@code value} in every {@code {{key}}} placeholder without
* HTML-escaping. Useful for injecting "sub-templates".
* @param key the placeholder name
* @param value the value to inject
* @return this instance for further templating
*/
HtmlTemplates.Builder withRawHtml(String key, String value) {
if (!value.isEmpty() && value.charAt(value.length() - 1) == '\n') {
value = value.substring(0, value.length() - 1);
}
this.values.put(key, value);
return this;
}
/**
* Render the template. All placeholders MUST have a corresponding value. If a
* placeholder does not have a corresponding value, throws
* {@link IllegalStateException}.
* @return the rendered template
*/
String render() {
String template = this.template;
for (String key : this.values.keySet()) {
String pattern = Pattern.quote("{{" + key + "}}");
template = template.replaceAll(pattern, this.values.get(key));
}
String unusedPlaceholders = Pattern.compile("\\{\\{([a-zA-Z0-9]+)}}")
.matcher(template)
.results()
.map((result) -> result.group(1))
.collect(Collectors.joining(", "));
if (StringUtils.hasLength(unusedPlaceholders)) {
throw new IllegalStateException("Unused placeholders in template: [%s]".formatted(unusedPlaceholders));
}
return template;
}
}
}
@@ -1,52 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.registration;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
import org.springframework.util.Assert;
public class HttpSessionPublicKeyCredentialCreationOptionsRepository
implements PublicKeyCredentialCreationOptionsRepository {
static final String DEFAULT_ATTR_NAME = PublicKeyCredentialCreationOptions.class.getName().concat("ATTR_NAME");
private String attrName = DEFAULT_ATTR_NAME;
@Override
public void save(HttpServletRequest request, HttpServletResponse response,
PublicKeyCredentialCreationOptions options) {
request.getSession().setAttribute(this.attrName, options);
}
public PublicKeyCredentialCreationOptions load(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return null;
}
return (PublicKeyCredentialCreationOptions) session.getAttribute(this.attrName);
}
public void setAttrName(String attrName) {
Assert.notNull(attrName, "attrName cannot be null");
this.attrName = attrName;
}
}
@@ -1,142 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.registration;
import java.io.IOException;
import java.util.function.Supplier;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
import org.springframework.security.web.webauthn.jackson.WebauthnJackson2Module;
import org.springframework.security.web.webauthn.management.ImmutablePublicKeyCredentialCreationOptionsRequest;
import org.springframework.security.web.webauthn.management.WebAuthnRelyingPartyOperations;
import org.springframework.util.Assert;
import org.springframework.web.filter.OncePerRequestFilter;
/**
* A {@link jakarta.servlet.Filter} that renders the
* {@link PublicKeyCredentialCreationOptions} for <a href=
* "https://w3c.github.io/webappsec-credential-management/#dom-credentialscontainer-create">creating</a>
* a new credential.
*
* @author DingHao
*/
public class PublicKeyCredentialCreationOptionsFilter extends OncePerRequestFilter {
private PublicKeyCredentialCreationOptionsRepository repository = new HttpSessionPublicKeyCredentialCreationOptionsRepository();
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
.getContextHolderStrategy();
private RequestMatcher matcher = PathPatternRequestMatcher.withDefaults()
.matcher(HttpMethod.POST, "/webauthn/register/options");
private AuthorizationManager<HttpServletRequest> authorization = AuthenticatedAuthorizationManager.authenticated();
private final WebAuthnRelyingPartyOperations rpOperations;
private HttpMessageConverter<Object> converter = new MappingJackson2HttpMessageConverter(
Jackson2ObjectMapperBuilder.json().modules(new WebauthnJackson2Module()).build());
/**
* Creates a new instance.
* @param rpOperations the {@link WebAuthnRelyingPartyOperations} to use. Cannot be
* null.
*/
public PublicKeyCredentialCreationOptionsFilter(WebAuthnRelyingPartyOperations rpOperations) {
Assert.notNull(rpOperations, "rpOperations cannot be null");
this.rpOperations = rpOperations;
}
/**
* Sets the {@link RequestMatcher} used to trigger this filter.
* <p>
* By default, the {@link RequestMatcher} is {@code POST /webauthn/register/options}.
* @param requestMatcher the {@link RequestMatcher} to use
* @since 6.5
*/
public void setRequestMatcher(RequestMatcher requestMatcher) {
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
this.matcher = requestMatcher;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (!this.matcher.matches(request)) {
filterChain.doFilter(request, response);
return;
}
Supplier<SecurityContext> context = this.securityContextHolderStrategy.getDeferredContext();
Supplier<Authentication> authentication = () -> context.get().getAuthentication();
AuthorizationResult result = this.authorization.authorize(authentication, request);
if (result == null || !result.isGranted()) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return;
}
PublicKeyCredentialCreationOptions options = this.rpOperations.createPublicKeyCredentialCreationOptions(
new ImmutablePublicKeyCredentialCreationOptionsRequest(authentication.get()));
this.repository.save(request, response, options);
response.setStatus(HttpServletResponse.SC_OK);
response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
this.converter.write(options, MediaType.APPLICATION_JSON, new ServletServerHttpResponse(response));
}
/**
* Sets the {@link PublicKeyCredentialCreationOptionsRepository} to use. The default
* is {@link HttpSessionPublicKeyCredentialCreationOptionsRepository}.
* @param creationOptionsRepository the
* {@link PublicKeyCredentialCreationOptionsRepository} to use. Cannot be null.
*/
public void setCreationOptionsRepository(PublicKeyCredentialCreationOptionsRepository creationOptionsRepository) {
Assert.notNull(creationOptionsRepository, "creationOptionsRepository cannot be null");
this.repository = creationOptionsRepository;
}
/**
* Set the {@link HttpMessageConverter} to read the
* {@link WebAuthnRegistrationFilter.WebAuthnRegistrationRequest} and write the
* response. The default is {@link MappingJackson2HttpMessageConverter}.
* @param converter the {@link HttpMessageConverter} to use. Cannot be null.
*/
public void setConverter(HttpMessageConverter<Object> converter) {
Assert.notNull(converter, "converter cannot be null");
this.converter = converter;
}
}
@@ -1,52 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.registration;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
/**
* Saves {@link PublicKeyCredentialCreationOptions} between a request to generate an
* assertion and the validation of the assertion.
*
* @author Rob Winch
* @since 6.4
*/
public interface PublicKeyCredentialCreationOptionsRepository {
/**
* Saves the provided {@link PublicKeyCredentialCreationOptions} or clears an existing
* {@link PublicKeyCredentialCreationOptions} if {@code options} is null.
* @param request the {@link HttpServletRequest}
* @param response the {@link HttpServletResponse}
* @param options the {@link PublicKeyCredentialCreationOptions} to save or null if an
* existing {@link PublicKeyCredentialCreationOptions} should be removed.
*/
void save(HttpServletRequest request, HttpServletResponse response, PublicKeyCredentialCreationOptions options);
/**
* Gets a saved {@link PublicKeyCredentialCreationOptions} if it exists, otherwise
* null.
* @param request the {@link HttpServletRequest}
* @return the {@link PublicKeyCredentialCreationOptions} that was saved, otherwise
* null.
*/
PublicKeyCredentialCreationOptions load(HttpServletRequest request);
}
@@ -1,238 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.webauthn.registration;
import java.io.IOException;
import com.fasterxml.jackson.databind.json.JsonMapper;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.security.web.webauthn.api.Bytes;
import org.springframework.security.web.webauthn.api.CredentialRecord;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
import org.springframework.security.web.webauthn.jackson.WebauthnJackson2Module;
import org.springframework.security.web.webauthn.management.ImmutableRelyingPartyRegistrationRequest;
import org.springframework.security.web.webauthn.management.RelyingPartyPublicKey;
import org.springframework.security.web.webauthn.management.UserCredentialRepository;
import org.springframework.security.web.webauthn.management.WebAuthnRelyingPartyOperations;
import org.springframework.util.Assert;
import org.springframework.web.filter.OncePerRequestFilter;
/**
* Authenticates {@code PublicKeyCredential<AuthenticatorAssertionResponse>} that is
* parsed from the body of the {@link HttpServletRequest} using the
* {@link #setConverter(HttpMessageConverter)}. An example request is provided below:
*
* <pre>
* {
* "publicKey": {
* "credential": {
* "id": "dYF7EGnRFFIXkpXi9XU2wg",
* "rawId": "dYF7EGnRFFIXkpXi9XU2wg",
* "response": {
* "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViUy9GqwTRaMpzVDbXq1dyEAXVOxrou08k22ggRC45MKNhdAAAAALraVWanqkAfvZZFYZpVEg0AEHWBexBp0RRSF5KV4vV1NsKlAQIDJiABIVggQjmrekPGzyqtoKK9HPUH-8Z2FLpoqkklFpFPQVICQ3IiWCD6I9Jvmor685fOZOyGXqUd87tXfvJk8rxj9OhuZvUALA",
* "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiSl9RTi10SFJYRWVKYjlNcUNrWmFPLUdOVmlibXpGVGVWMk43Z0ptQUdrQSIsIm9yaWdpbiI6Imh0dHBzOi8vZXhhbXBsZS5sb2NhbGhvc3Q6ODQ0MyIsImNyb3NzT3JpZ2luIjpmYWxzZX0",
* "transports": [
* "internal",
* "hybrid"
* ]
* },
* "type": "public-key",
* "clientExtensionResults": {},
* "authenticatorAttachment": "platform"
* },
* "label": "1password"
* }
* </pre>
*
* @author Rob Winch
* @since 6.4
*/
public class WebAuthnRegistrationFilter extends OncePerRequestFilter {
static final String DEFAULT_REGISTER_CREDENTIAL_URL = "/webauthn/register";
private static final Log logger = LogFactory.getLog(WebAuthnRegistrationFilter.class);
private final WebAuthnRelyingPartyOperations rpOptions;
private final UserCredentialRepository userCredentials;
private HttpMessageConverter<Object> converter = new MappingJackson2HttpMessageConverter(
JsonMapper.builder().addModule(new WebauthnJackson2Module()).build());
private PublicKeyCredentialCreationOptionsRepository creationOptionsRepository = new HttpSessionPublicKeyCredentialCreationOptionsRepository();
private RequestMatcher registerCredentialMatcher = PathPatternRequestMatcher.withDefaults()
.matcher(HttpMethod.POST, DEFAULT_REGISTER_CREDENTIAL_URL);
private RequestMatcher removeCredentialMatcher = PathPatternRequestMatcher.withDefaults()
.matcher(HttpMethod.DELETE, "/webauthn/register/{id}");
public WebAuthnRegistrationFilter(UserCredentialRepository userCredentials,
WebAuthnRelyingPartyOperations rpOptions) {
Assert.notNull(userCredentials, "userCredentials must not be null");
Assert.notNull(rpOptions, "rpOptions must not be null");
this.userCredentials = userCredentials;
this.rpOptions = rpOptions;
}
/**
* Sets the {@link RequestMatcher} to trigger this filter's the credential
* registration operation .
* <p/>
* By default, the {@link RequestMatcher} is {@code POST /webauthn/register}.
* @param registerCredentialMatcher the {@link RequestMatcher} to use
* @since 6.5
*/
public void setRegisterCredentialMatcher(RequestMatcher registerCredentialMatcher) {
Assert.notNull(registerCredentialMatcher, "registerCredentialMatcher cannot be null");
this.registerCredentialMatcher = registerCredentialMatcher;
}
/**
* Sets the {@link RequestMatcher} to trigger this filter's the credential removal
* operation .
* <p/>
* By default, the {@link RequestMatcher} is {@code DELETE /webauthn/register/{id}}.
* @param removeCredentialMatcher the {@link RequestMatcher} to use
* @since 6.5
*/
public void setRemoveCredentialMatcher(RequestMatcher removeCredentialMatcher) {
Assert.notNull(removeCredentialMatcher, "removeCredentialMatcher cannot be null");
this.removeCredentialMatcher = removeCredentialMatcher;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (this.registerCredentialMatcher.matches(request)) {
registerCredential(request, response);
return;
}
RequestMatcher.MatchResult removeMatchResult = this.removeCredentialMatcher.matcher(request);
if (removeMatchResult.isMatch()) {
String id = removeMatchResult.getVariables().get("id");
removeCredential(request, response, id);
return;
}
filterChain.doFilter(request, response);
}
/**
* Set the {@link HttpMessageConverter} to read the
* {@link WebAuthnRegistrationRequest} and write the response. The default is
* {@link MappingJackson2HttpMessageConverter}.
* @param converter the {@link HttpMessageConverter} to use. Cannot be null.
*/
public void setConverter(HttpMessageConverter<Object> converter) {
Assert.notNull(converter, "converter cannot be null");
this.converter = converter;
}
/**
* Sets the {@link PublicKeyCredentialCreationOptionsRepository} to use. The default
* is {@link HttpSessionPublicKeyCredentialCreationOptionsRepository}.
* @param creationOptionsRepository the
* {@link PublicKeyCredentialCreationOptionsRepository} to use. Cannot be null.
*/
public void setCreationOptionsRepository(PublicKeyCredentialCreationOptionsRepository creationOptionsRepository) {
Assert.notNull(creationOptionsRepository, "creationOptionsRepository cannot be null");
this.creationOptionsRepository = creationOptionsRepository;
}
private void registerCredential(HttpServletRequest request, HttpServletResponse response) throws IOException {
WebAuthnRegistrationRequest registrationRequest = readRegistrationRequest(request);
if (registrationRequest == null) {
response.setStatus(HttpStatus.BAD_REQUEST.value());
return;
}
PublicKeyCredentialCreationOptions options = this.creationOptionsRepository.load(request);
if (options == null) {
response.setStatus(HttpStatus.BAD_REQUEST.value());
return;
}
this.creationOptionsRepository.save(request, response, null);
CredentialRecord credentialRecord = this.rpOptions.registerCredential(
new ImmutableRelyingPartyRegistrationRequest(options, registrationRequest.getPublicKey()));
SuccessfulUserRegistrationResponse registrationResponse = new SuccessfulUserRegistrationResponse(
credentialRecord);
ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
this.converter.write(registrationResponse, MediaType.APPLICATION_JSON, outputMessage);
}
private WebAuthnRegistrationRequest readRegistrationRequest(HttpServletRequest request) {
HttpInputMessage inputMessage = new ServletServerHttpRequest(request);
try {
return (WebAuthnRegistrationRequest) this.converter.read(WebAuthnRegistrationRequest.class, inputMessage);
}
catch (Exception ex) {
logger.debug("Unable to parse WebAuthnRegistrationRequest", ex);
return null;
}
}
private void removeCredential(HttpServletRequest request, HttpServletResponse response, String id)
throws IOException {
this.userCredentials.delete(Bytes.fromBase64(id));
response.setStatus(HttpStatus.NO_CONTENT.value());
}
static class WebAuthnRegistrationRequest {
private RelyingPartyPublicKey publicKey;
RelyingPartyPublicKey getPublicKey() {
return this.publicKey;
}
void setPublicKey(RelyingPartyPublicKey publicKey) {
this.publicKey = publicKey;
}
}
public static class SuccessfulUserRegistrationResponse {
private final CredentialRecord credentialRecord;
SuccessfulUserRegistrationResponse(CredentialRecord credentialRecord) {
this.credentialRecord = credentialRecord;
}
public boolean isSuccess() {
return true;
}
}
}

Some files were not shown because too many files have changed in this diff Show More