Polish AllRequiredFactorsAuthorizationManager.anyOf
- Add validation - Extract to static inner class - Uniqueness determined by Set rather than requiredFactor This is important for the failure with the same RequiredFactor, but a different reason - Add documentation Signed-off-by: Robert Winch <362503+rwinch@users.noreply.github.com>
This commit is contained in:
@@ -125,6 +125,23 @@ include-code::./ValidDurationConfiguration[tag=httpSecurity,indent=0]
|
||||
<5> Otherwise, authentication is required, but it does not care if it is a password or how long ago authentication occurred
|
||||
<6> Set up the authentication mechanisms that can provide the required factors.
|
||||
|
||||
[[all-factors-anyof]]
|
||||
== AllRequiredFactorsAuthorizationManager.anyOf
|
||||
|
||||
In the previous examples, access requires satisfying that the user has authenticated with all factors.
|
||||
There are times when an application wants to allow users to satisfy one of several different combinations of factors.
|
||||
javadoc:org.springframework.security.authorization.AllRequiredFactorsAuthorizationManager#anyOf(AllRequiredFactorsAuthorizationManager...)[AllRequiredFactorsAuthorizationManager.anyOf] grants access if at least one of the provided combinations of factors is satisfied.
|
||||
|
||||
Consider a scenario where a user can authenticate with WebAuthn alone, or with both a password and a one-time token.
|
||||
|
||||
include-code::./AnyOfRequiredFactorsConfiguration[tag=httpSecurity,indent=0]
|
||||
<1> Require WebAuthn
|
||||
<2> Require both a password and a one-time token
|
||||
<3> Combine the combinations of factors with `anyOf`, granting access if either is satisfied
|
||||
<4> URLs that begin with `/protected/**` require the user to satisfy either combination of factors
|
||||
<5> All other requests require only authentication
|
||||
<6> Set up the authentication mechanisms that can provide the required factors
|
||||
|
||||
[[programmatic-mfa]]
|
||||
== Programmatic MFA
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
== Core
|
||||
|
||||
* https://github.com/spring-projects/spring-security/pull/18634[gh-18634] - Added javadoc:org.springframework.security.util.matcher.InetAddressMatcher[]
|
||||
* https://github.com/spring-projects/spring-security/issues/18960[gh-18960] - Added xref:servlet/authentication/mfa.adoc#all-factors-anyof[AllRequiredFactorsAuthorizationManager.anyOf]
|
||||
|
||||
== Web
|
||||
* https://github.com/spring-projects/spring-security/issues/18755[gh-18755] - Include `charset` in `WWW-Authenticate` header
|
||||
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
package org.springframework.security.docs.servlet.authentication.allfactorsanyof;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authorization.AllRequiredFactorsAuthorizationManager;
|
||||
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler;
|
||||
import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler;
|
||||
|
||||
@EnableWebSecurity
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class AnyOfRequiredFactorsConfiguration {
|
||||
|
||||
// tag::httpSecurity[]
|
||||
@Bean
|
||||
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
// <1>
|
||||
AllRequiredFactorsAuthorizationManager<Object> webauthn = AllRequiredFactorsAuthorizationManager
|
||||
.<Object>builder()
|
||||
.requireFactor((factor) -> factor.webauthnAuthority())
|
||||
.build();
|
||||
// <2>
|
||||
AllRequiredFactorsAuthorizationManager<Object> passwordAndOtt = AllRequiredFactorsAuthorizationManager
|
||||
.<Object>builder()
|
||||
.requireFactor((factor) -> factor.passwordAuthority())
|
||||
.requireFactor((factor) -> factor.ottAuthority())
|
||||
.build();
|
||||
// <3>
|
||||
DefaultAuthorizationManagerFactory<Object> mfa = new DefaultAuthorizationManagerFactory<>();
|
||||
mfa.setAdditionalAuthorization(AllRequiredFactorsAuthorizationManager.anyOf(webauthn, passwordAndOtt));
|
||||
http
|
||||
.authorizeHttpRequests((authorize) -> authorize
|
||||
// <4>
|
||||
.requestMatchers("/protected/**").access(mfa.authenticated())
|
||||
// <5>
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
// <6>
|
||||
.formLogin(Customizer.withDefaults())
|
||||
.oneTimeTokenLogin(Customizer.withDefaults())
|
||||
.webAuthn((webAuthn) -> webAuthn
|
||||
.rpName("Spring Security")
|
||||
.rpId("example.com")
|
||||
.allowedOrigins("https://example.com")
|
||||
);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
// end::httpSecurity[]
|
||||
|
||||
@Bean
|
||||
UserDetailsService userDetailsService() {
|
||||
return new InMemoryUserDetailsManager(
|
||||
User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.authorities("app")
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
@Bean
|
||||
OneTimeTokenGenerationSuccessHandler tokenGenerationSuccessHandler() {
|
||||
return new RedirectOneTimeTokenGenerationSuccessHandler("/ott/sent");
|
||||
}
|
||||
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package org.springframework.security.kt.docs.servlet.authentication.allfactorsanyof
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.authorization.AllRequiredFactorsAuthorizationManager
|
||||
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.security.core.userdetails.UserDetailsService
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager
|
||||
import org.springframework.security.web.SecurityFilterChain
|
||||
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler
|
||||
import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler
|
||||
|
||||
@EnableWebSecurity
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
internal class AnyOfRequiredFactorsConfiguration {
|
||||
|
||||
// tag::httpSecurity[]
|
||||
@Bean
|
||||
@Throws(Exception::class)
|
||||
fun springSecurity(http: HttpSecurity): SecurityFilterChain? {
|
||||
// @formatter:off
|
||||
// <1>
|
||||
val webauthn = AllRequiredFactorsAuthorizationManager.builder<Any>()
|
||||
.requireFactor { factor -> factor.webauthnAuthority() }
|
||||
.build()
|
||||
// <2>
|
||||
val passwordAndOtt = AllRequiredFactorsAuthorizationManager.builder<Any>()
|
||||
.requireFactor { factor -> factor.passwordAuthority() }
|
||||
.requireFactor { factor -> factor.ottAuthority() }
|
||||
.build()
|
||||
// <3>
|
||||
val mfa = DefaultAuthorizationManagerFactory<Any>()
|
||||
mfa.setAdditionalAuthorization(AllRequiredFactorsAuthorizationManager.anyOf(webauthn, passwordAndOtt))
|
||||
http {
|
||||
authorizeHttpRequests {
|
||||
// <4>
|
||||
authorize("/protected/**", mfa.authenticated())
|
||||
// <5>
|
||||
authorize(anyRequest, authenticated)
|
||||
}
|
||||
// <6>
|
||||
formLogin { }
|
||||
oneTimeTokenLogin { }
|
||||
webAuthn {
|
||||
rpName = "Spring Security"
|
||||
rpId = "example.com"
|
||||
allowedOrigins = setOf("https://example.com")
|
||||
}
|
||||
}
|
||||
// @formatter:on
|
||||
return http.build()
|
||||
}
|
||||
|
||||
// end::httpSecurity[]
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@Bean
|
||||
fun userDetailsService(): UserDetailsService {
|
||||
return InMemoryUserDetailsManager(
|
||||
User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.authorities("app")
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun tokenGenerationSuccessHandler(): OneTimeTokenGenerationSuccessHandler {
|
||||
return RedirectOneTimeTokenGenerationSuccessHandler("/ott/sent")
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user