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

Add MultiFactorCondition.WEBAUTHN_REGISTERED

Closes gh-18923
This commit is contained in:
Robert Winch
2026-03-13 22:30:19 -05:00
parent bd7171140e
commit ea2f2302da
12 changed files with 417 additions and 3 deletions
@@ -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<AdditionalRequiredFactorsBuilder<Object>>`] 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
+1
View File
@@ -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
+1
View File
@@ -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'
@@ -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<AuthorizationManagerFactories.AdditionalRequiredFactorsBuilder<Object>> additionalRequiredFactorsCustomizer() {
return (builder) -> builder.when((auth) -> "admin".equals(auth.getName()));
}
// end::customizer[]
}
@@ -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[]
@@ -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<AuthorizationManagerFactories.AdditionalRequiredFactorsBuilder<Any>> {
return Customizer { builder -> builder.`when` { auth -> "admin" == auth.name } }
}
// end::customizer[]
}
@@ -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[]