diff --git a/config/src/main/java/org/springframework/security/config/annotation/authorization/EnableMultiFactorAuthentication.java b/config/src/main/java/org/springframework/security/config/annotation/authorization/EnableMultiFactorAuthentication.java index 2e04ef708e..ebcd7b2003 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/authorization/EnableMultiFactorAuthentication.java +++ b/config/src/main/java/org/springframework/security/config/annotation/authorization/EnableMultiFactorAuthentication.java @@ -30,8 +30,12 @@ import org.springframework.security.authorization.DefaultAuthorizationManagerFac * * When {@link #authorities()} is specified creates a * {@link DefaultAuthorizationManagerFactory} as a Bean with the {@link #authorities()} - * specified as additional required authorities. The configuration will be picked up by - * both + * specified as additional required authorities. When {@link #when()} is + * {@link MultiFactorCondition#WEBAUTHN_REGISTERED}, {@link #authorities()} must include + * {@link org.springframework.security.core.authority.FactorGrantedAuthority#WEBAUTHN_AUTHORITY}; + * otherwise an {@link IllegalArgumentException} is thrown during configuration + * processing. When {@link #when()} is not specified (default is an empty array), no such + * requirement applies. The configuration will be picked up by both * {@link org.springframework.security.config.annotation.web.configuration.EnableWebSecurity} * and * {@link org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity}. @@ -80,4 +84,15 @@ public @interface EnableMultiFactorAuthentication { */ String[] authorities(); + /** + * The conditions under which multi-factor authentication is required. + *

+ * When multiple conditions are specified, they are applied as an AND (all conditions + * must be met). + * @return the conditions (default is an empty array, which requires MFA + * unconditionally) + * @since 7.1 + */ + MultiFactorCondition[] when() default {}; + } diff --git a/config/src/main/java/org/springframework/security/config/annotation/authorization/MultiFactorAuthenticationSelector.java b/config/src/main/java/org/springframework/security/config/annotation/authorization/MultiFactorAuthenticationSelector.java index 2f1d44ec8a..b1a3433ab0 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/authorization/MultiFactorAuthenticationSelector.java +++ b/config/src/main/java/org/springframework/security/config/annotation/authorization/MultiFactorAuthenticationSelector.java @@ -17,16 +17,24 @@ package org.springframework.security.config.annotation.authorization; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import org.springframework.context.annotation.ImportSelector; import org.springframework.core.type.AnnotationMetadata; import org.springframework.security.authorization.DefaultAuthorizationManagerFactory; +import org.springframework.security.core.authority.FactorGrantedAuthority; /** * Uses {@link EnableMultiFactorAuthentication} to configure a * {@link DefaultAuthorizationManagerFactory}. + *

+ * When {@link EnableMultiFactorAuthentication#when()} includes + * {@link MultiFactorCondition#WEBAUTHN_REGISTERED}, validates that + * {@link EnableMultiFactorAuthentication#authorities()} includes + * {@link org.springframework.security.core.authority.FactorGrantedAuthority#WEBAUTHN_AUTHORITY} + * and throws an {@link IllegalArgumentException} if not. * * @author Rob Winch * @since 7.0 @@ -39,9 +47,19 @@ class MultiFactorAuthenticationSelector implements ImportSelector { Map multiFactorAuthenticationAttrs = metadata .getAnnotationAttributes(EnableMultiFactorAuthentication.class.getName()); String[] authorities = (String[]) multiFactorAuthenticationAttrs.getOrDefault("authorities", new String[0]); - List imports = new ArrayList<>(2); + MultiFactorCondition[] when = (MultiFactorCondition[]) multiFactorAuthenticationAttrs.getOrDefault("when", + new MultiFactorCondition[0]); + boolean hasWebAuthn = Arrays.asList(when).contains(MultiFactorCondition.WEBAUTHN_REGISTERED); + if (hasWebAuthn && !Arrays.asList(authorities).contains(FactorGrantedAuthority.WEBAUTHN_AUTHORITY)) { + throw new IllegalArgumentException("When when() includes " + MultiFactorCondition.WEBAUTHN_REGISTERED + + ", authorities() must include " + FactorGrantedAuthority.WEBAUTHN_AUTHORITY); + } + List imports = new ArrayList<>(3); if (authorities.length > 0) { imports.add(AuthorizationManagerFactoryConfiguration.class.getName()); + if (hasWebAuthn) { + imports.add(WhenWebAuthnRegisteredMfaConfiguration.class.getName()); + } } imports.add(EnableMfaFiltersConfiguration.class.getName()); return imports.toArray(new String[imports.size()]); diff --git a/config/src/main/java/org/springframework/security/config/annotation/authorization/MultiFactorCondition.java b/config/src/main/java/org/springframework/security/config/annotation/authorization/MultiFactorCondition.java new file mode 100644 index 0000000000..e365330fb0 --- /dev/null +++ b/config/src/main/java/org/springframework/security/config/annotation/authorization/MultiFactorCondition.java @@ -0,0 +1,58 @@ +/* + * Copyright 2004-present 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.config.annotation.authorization; + +/** + * Condition under which multi-factor authentication is required. + * + * @author Rob Winch + * @since 7.1 + * @see EnableMultiFactorAuthentication#when() + */ +public enum MultiFactorCondition { + + /** + * Require multi-factor authentication only when the user has a WebAuthn credential + * record registered. + *

+ * When this condition is specified, + * {@link EnableMultiFactorAuthentication#authorities()} must include + * {@link org.springframework.security.core.authority.FactorGrantedAuthority#WEBAUTHN_AUTHORITY}. + * Failing to include it results in an {@link IllegalArgumentException} when the + * configuration is processed. + *

+ * Using this condition also requires both a + * {@link org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository} + * Bean and a + * {@link org.springframework.security.web.webauthn.management.UserCredentialRepository} + * Bean to be published. + * + *

+	 * @Bean
+	 * public PublicKeyCredentialUserEntityRepository userEntityRepository() {
+	 *     return new InMemoryPublicKeyCredentialUserEntityRepository();
+	 * }
+	 *
+	 * @Bean
+	 * public UserCredentialRepository userCredentialRepository() {
+	 *     return new InMemoryUserCredentialRepository();
+	 * }
+	 * 
+ */ + WEBAUTHN_REGISTERED + +} diff --git a/config/src/main/java/org/springframework/security/config/annotation/authorization/WhenWebAuthnRegisteredMfaConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/authorization/WhenWebAuthnRegisteredMfaConfiguration.java new file mode 100644 index 0000000000..a213710b5f --- /dev/null +++ b/config/src/main/java/org/springframework/security/config/annotation/authorization/WhenWebAuthnRegisteredMfaConfiguration.java @@ -0,0 +1,90 @@ +/* + * Copyright 2004-present 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.config.annotation.authorization; + +import java.util.function.Predicate; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authorization.AuthorizationManagerFactories.AdditionalRequiredFactorsBuilder; +import org.springframework.security.config.Customizer; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity; +import org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository; +import org.springframework.security.web.webauthn.management.UserCredentialRepository; + +/** + * Configuration that provides a + * {@link Customizer}<{@link AdditionalRequiredFactorsBuilder}> for + * {@link MultiFactorCondition#WEBAUTHN_REGISTERED}, requiring multi-factor authentication + * only when the user has a WebAuthn credential record. + * + * @author Rob Winch + * @since 7.1 + * @see EnableMultiFactorAuthentication#when() + * @see MultiFactorCondition#WEBAUTHN_REGISTERED + */ +@Configuration(proxyBeanMethods = false) +class WhenWebAuthnRegisteredMfaConfiguration { + + @Bean + Customizer> additionalRequiredFactorsCustomizer( + PublicKeyCredentialUserEntityRepository userEntityRepository, + UserCredentialRepository userCredentialRepository) { + return (builder) -> builder.withWhen((current) -> { + Predicate webAuthnRegisteredPredicate = new WebAuthnRegisteredPredicate( + userEntityRepository, userCredentialRepository); + if (current == null) { + return webAuthnRegisteredPredicate; + } + return current.and(webAuthnRegisteredPredicate); + }); + } + + private static final class WebAuthnRegisteredPredicate implements Predicate { + + private final PublicKeyCredentialUserEntityRepository userEntityRepository; + + private final UserCredentialRepository userCredentialRepository; + + private WebAuthnRegisteredPredicate(PublicKeyCredentialUserEntityRepository userEntityRepository, + UserCredentialRepository userCredentialRepository) { + this.userEntityRepository = userEntityRepository; + this.userCredentialRepository = userCredentialRepository; + } + + @Override + public boolean test(Authentication authentication) { + if (authentication == null || authentication.getName() == null) { + return false; + } + PublicKeyCredentialUserEntity userEntity = this.userEntityRepository + .findByUsername(authentication.getName()); + if (userEntity == null) { + return false; + } + return !this.userCredentialRepository.findByUserId(userEntity.getId()).isEmpty(); + } + + @Override + public String toString() { + return "WEBAUTHN_REGISTERED"; + } + + } + +} diff --git a/config/src/test/java/org/springframework/security/config/annotation/authorization/MultiFactorAuthenticationSelectorTests.java b/config/src/test/java/org/springframework/security/config/annotation/authorization/MultiFactorAuthenticationSelectorTests.java new file mode 100644 index 0000000000..03080492bc --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/annotation/authorization/MultiFactorAuthenticationSelectorTests.java @@ -0,0 +1,108 @@ +/* + * Copyright 2004-present 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.config.annotation.authorization; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.security.core.authority.FactorGrantedAuthority; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link MultiFactorAuthenticationSelector}. + * + * @author Rob Winch + */ +class MultiFactorAuthenticationSelectorTests { + + private final MultiFactorAuthenticationSelector selector = new MultiFactorAuthenticationSelector(); + + @Test + void selectImportsWhenWhenIsEmptyAndAuthoritiesSpecifiedThenReturnsImportsWithoutWebAuthnConfig() { + AnnotationMetadata metadata = metadata(new MultiFactorCondition[0], FactorGrantedAuthority.OTT_AUTHORITY, + FactorGrantedAuthority.PASSWORD_AUTHORITY); + String[] imports = this.selector.selectImports(metadata); + assertThat(imports).isNotEmpty(); + assertThat(imports).doesNotContain(WhenWebAuthnRegisteredMfaConfiguration.class.getName()); + } + + @Test + void selectImportsWhenWhenOmittedThenDefaultsToEmptyAndReturnsImports() { + AnnotationMetadata metadata = metadataWithoutWhen(FactorGrantedAuthority.OTT_AUTHORITY, + FactorGrantedAuthority.PASSWORD_AUTHORITY); + String[] imports = this.selector.selectImports(metadata); + assertThat(imports).isNotEmpty(); + assertThat(imports).doesNotContain(WhenWebAuthnRegisteredMfaConfiguration.class.getName()); + } + + @Test + void selectImportsWhenHasWebAuthnConditionAndAuthoritiesIncludesWebAuthnThenReturnsImports() { + AnnotationMetadata metadata = metadata(new MultiFactorCondition[] { MultiFactorCondition.WEBAUTHN_REGISTERED }, + FactorGrantedAuthority.OTT_AUTHORITY, FactorGrantedAuthority.PASSWORD_AUTHORITY, + FactorGrantedAuthority.WEBAUTHN_AUTHORITY); + String[] imports = this.selector.selectImports(metadata); + assertThat(imports).isNotEmpty(); + } + + @Test + void selectImportsWhenHasWebAuthnConditionAndAuthoritiesOnlyWebAuthnThenReturnsImports() { + AnnotationMetadata metadata = metadata(new MultiFactorCondition[] { MultiFactorCondition.WEBAUTHN_REGISTERED }, + FactorGrantedAuthority.WEBAUTHN_AUTHORITY); + String[] imports = this.selector.selectImports(metadata); + assertThat(imports).isNotEmpty(); + } + + @Test + void selectImportsWhenHasWebAuthnConditionAndAuthoritiesEmptyThenThrowsException() { + AnnotationMetadata metadata = metadata(new MultiFactorCondition[] { MultiFactorCondition.WEBAUTHN_REGISTERED }); + assertThatIllegalArgumentException().isThrownBy(() -> this.selector.selectImports(metadata)) + .withMessageContaining("authorities() must include " + FactorGrantedAuthority.WEBAUTHN_AUTHORITY); + } + + @Test + void selectImportsWhenHasWebAuthnConditionAndAuthoritiesExcludesWebAuthnThenThrowsException() { + AnnotationMetadata metadata = metadata(new MultiFactorCondition[] { MultiFactorCondition.WEBAUTHN_REGISTERED }, + FactorGrantedAuthority.OTT_AUTHORITY, FactorGrantedAuthority.PASSWORD_AUTHORITY); + assertThatIllegalArgumentException().isThrownBy(() -> this.selector.selectImports(metadata)) + .withMessageContaining("authorities() must include " + FactorGrantedAuthority.WEBAUTHN_AUTHORITY); + } + + private static AnnotationMetadata metadata(MultiFactorCondition[] when, String... authorities) { + AnnotationMetadata metadata = mock(AnnotationMetadata.class); + Map attrs = new HashMap<>(); + attrs.put("authorities", authorities); + attrs.put("when", when); + given(metadata.getAnnotationAttributes(EnableMultiFactorAuthentication.class.getName())).willReturn(attrs); + return metadata; + } + + private static AnnotationMetadata metadataWithoutWhen(String... authorities) { + AnnotationMetadata metadata = mock(AnnotationMetadata.class); + Map attrs = new HashMap<>(); + attrs.put("authorities", authorities); + given(metadata.getAnnotationAttributes(EnableMultiFactorAuthentication.class.getName())).willReturn(attrs); + return metadata; + } + +} diff --git a/docs/modules/ROOT/pages/servlet/authentication/mfa.adoc b/docs/modules/ROOT/pages/servlet/authentication/mfa.adoc index d2f7e0d054..6de25b4c78 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/mfa.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/mfa.adoc @@ -37,6 +37,19 @@ Spring Security behind the scenes knows which endpoint to go to depending on whi If the user logged in initially with their username and password, then Spring Security redirects to the One-Time-Token Login page. If the user logged in initially with a token, then Spring Security redirects to the Username/Password Login page. +[[mfa-when-webauthn-registered]] +=== Conditionally Requiring MFA for WebAuthn Users + +At times, you may want to conditionally require MFA only for users who have registered a WebAuthn credential (passkey). +You can achieve this by specifying javadoc:org.springframework.security.config.annotation.authorization.EnableMultiFactorAuthentication#when()[when = MultiFactorCondition.WEBAUTHN_REGISTERED]. + +include-code::./WebAuthnConditionConfiguration[tag=enable-mfa-webauthn,indent=0] + +This configuration requires `FACTOR_WEBAUTHN` and `FACTOR_PASSWORD` only when the user has registered a passkey. +It works by publishing a xref:./mfa.adoc#mfa-when-custom-conditions[`Customizer>`] that updates the xref:./mfa.adoc#programmatic-mfa[`withWhen`] method with the condition. + +NOTE: This condition requires both a javadoc:org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository[] bean and a javadoc:org.springframework.security.web.webauthn.management.UserCredentialRepository[] bean to be published in order to determine if the user has registered a WebAuthn credential. + [[mfa-when-custom-conditions]] === Custom MFA Conditions diff --git a/docs/modules/ROOT/pages/whats-new.adoc b/docs/modules/ROOT/pages/whats-new.adoc index a89ff9aacc..b0c2feed25 100644 --- a/docs/modules/ROOT/pages/whats-new.adoc +++ b/docs/modules/ROOT/pages/whats-new.adoc @@ -7,6 +7,7 @@ * https://github.com/spring-projects/spring-security/issues/18755[gh-18755] - Include `charset` in `WWW-Authenticate` header * Added xref:servlet/authorization/architecture.adoc#authz-conditional-authorization-manager[ConditionalAuthorizationManager] * Added `when` and `withWhen` conditions to `AuthorizationManagerFactories.multiFactor()` for xref:servlet/authentication/mfa.adoc#programmatic-mfa[Programmatic MFA] +* Added `MultiFactorCondition.WEBAUTHN_REGISTERED` to `@EnableMultiFactorAuthentication(when = ...)` for xref:servlet/authentication/mfa.adoc#mfa-when-webauthn-registered[conditionally requiring MFA for WebAuthn Users] == OAuth 2.0 diff --git a/docs/spring-security-docs.gradle b/docs/spring-security-docs.gradle index d32a956ac2..02cdff6966 100644 --- a/docs/spring-security-docs.gradle +++ b/docs/spring-security-docs.gradle @@ -44,6 +44,7 @@ dependencies { testImplementation project(':spring-security-oauth2-client') testImplementation project(':spring-security-oauth2-resource-server') testImplementation project(':spring-security-messaging') + testImplementation project(':spring-security-webauthn') testImplementation 'com.squareup.okhttp3:mockwebserver' testImplementation libs.com.password4j.password4j testImplementation 'com.unboundid:unboundid-ldapsdk' diff --git a/docs/src/test/java/org/springframework/security/docs/servlet/authentication/mfawhencustomconditions/CustomizerAuthorizationManagerFactoryConfiguration.java b/docs/src/test/java/org/springframework/security/docs/servlet/authentication/mfawhencustomconditions/CustomizerAuthorizationManagerFactoryConfiguration.java new file mode 100644 index 0000000000..40110557be --- /dev/null +++ b/docs/src/test/java/org/springframework/security/docs/servlet/authentication/mfawhencustomconditions/CustomizerAuthorizationManagerFactoryConfiguration.java @@ -0,0 +1,18 @@ +package org.springframework.security.docs.servlet.authentication.mfawhencustomconditions; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authorization.AuthorizationManagerFactories; +import org.springframework.security.config.Customizer; + +@Configuration(proxyBeanMethods = false) +class CustomizerAuthorizationManagerFactoryConfiguration { + + // tag::customizer[] + @Bean + Customizer> additionalRequiredFactorsCustomizer() { + return (builder) -> builder.when((auth) -> "admin".equals(auth.getName())); + } + // end::customizer[] + +} diff --git a/docs/src/test/java/org/springframework/security/docs/servlet/authentication/mfawhenwebauthnregistered/WebAuthnConditionConfiguration.java b/docs/src/test/java/org/springframework/security/docs/servlet/authentication/mfawhenwebauthnregistered/WebAuthnConditionConfiguration.java new file mode 100644 index 0000000000..f73289dbad --- /dev/null +++ b/docs/src/test/java/org/springframework/security/docs/servlet/authentication/mfawhenwebauthnregistered/WebAuthnConditionConfiguration.java @@ -0,0 +1,37 @@ +package org.springframework.security.docs.servlet.authentication.mfawhenwebauthnregistered; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authorization.EnableMultiFactorAuthentication; +import org.springframework.security.config.annotation.authorization.MultiFactorCondition; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.core.authority.FactorGrantedAuthority; +import org.springframework.security.web.webauthn.management.MapPublicKeyCredentialUserEntityRepository; +import org.springframework.security.web.webauthn.management.MapUserCredentialRepository; +import org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository; +import org.springframework.security.web.webauthn.management.UserCredentialRepository; + +@EnableWebSecurity +@Configuration(proxyBeanMethods = false) +// tag::enable-mfa-webauthn[] +@EnableMultiFactorAuthentication( + authorities = { + FactorGrantedAuthority.PASSWORD_AUTHORITY, + FactorGrantedAuthority.WEBAUTHN_AUTHORITY + }, + when = MultiFactorCondition.WEBAUTHN_REGISTERED +) +public class WebAuthnConditionConfiguration { + + @Bean + public PublicKeyCredentialUserEntityRepository userEntityRepository() { + return new MapPublicKeyCredentialUserEntityRepository(); + } + + @Bean + public UserCredentialRepository userCredentialRepository() { + return new MapUserCredentialRepository(); + } + +} +// end::enable-mfa-webauthn[] diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/mfawhencustomconditions/CustomizerAuthorizationManagerFactoryConfiguration.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/mfawhencustomconditions/CustomizerAuthorizationManagerFactoryConfiguration.kt new file mode 100644 index 0000000000..7d9fe9aab0 --- /dev/null +++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/mfawhencustomconditions/CustomizerAuthorizationManagerFactoryConfiguration.kt @@ -0,0 +1,18 @@ +package org.springframework.security.kt.docs.servlet.authentication.mfawhencustomconditions + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.security.authorization.AuthorizationManagerFactories +import org.springframework.security.config.Customizer + +@Configuration(proxyBeanMethods = false) +internal class CustomizerAuthorizationManagerFactoryConfiguration { + + // tag::customizer[] + @Bean + fun additionalRequiredFactorsCustomizer(): Customizer> { + return Customizer { builder -> builder.`when` { auth -> "admin" == auth.name } } + } + // end::customizer[] + +} diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/mfawhenwebauthnregistered/WebAuthnConditionConfiguration.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/mfawhenwebauthnregistered/WebAuthnConditionConfiguration.kt new file mode 100644 index 0000000000..a11b6faaa3 --- /dev/null +++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/mfawhenwebauthnregistered/WebAuthnConditionConfiguration.kt @@ -0,0 +1,37 @@ +package org.springframework.security.kt.docs.servlet.authentication.mfawhenwebauthnregistered + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.security.config.annotation.authorization.EnableMultiFactorAuthentication +import org.springframework.security.config.annotation.authorization.MultiFactorCondition +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity +import org.springframework.security.core.authority.FactorGrantedAuthority +import org.springframework.security.web.webauthn.management.MapPublicKeyCredentialUserEntityRepository +import org.springframework.security.web.webauthn.management.MapUserCredentialRepository +import org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository +import org.springframework.security.web.webauthn.management.UserCredentialRepository + +@EnableWebSecurity +@Configuration(proxyBeanMethods = false) +// tag::enable-mfa-webauthn[] +@EnableMultiFactorAuthentication( + authorities = [ + FactorGrantedAuthority.PASSWORD_AUTHORITY, + FactorGrantedAuthority.WEBAUTHN_AUTHORITY + ], + `when` = [MultiFactorCondition.WEBAUTHN_REGISTERED] +) +internal class WebAuthnConditionConfiguration { + + @Bean + fun userEntityRepository(): PublicKeyCredentialUserEntityRepository { + return MapPublicKeyCredentialUserEntityRepository() + } + + @Bean + fun userCredentialRepository(): UserCredentialRepository { + return MapUserCredentialRepository() + } + +} +// end::enable-mfa-webauthn[]