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

AuthorizationManagerFactories.when

Closes gh-18920
This commit is contained in:
Robert Winch
2026-03-11 13:56:04 -05:00
parent 8224b16caf
commit 28acf62936
6 changed files with 128 additions and 80 deletions
@@ -108,23 +108,20 @@ include-code::./ValidDurationConfiguration[tag=httpSecurity,indent=0]
In our previous examples, MFA is a static decision per request.
There are times when we might want to require MFA for some users, but not others.
Determining if MFA is enabled per user can be achieved by creating a custom `AuthorizationManager` that conditionally requires factors based upon the `Authentication`.
Determining if MFA is enabled per user can be achieved by using `AuthorizationManagerFactories.multiFactor().when` to conditionally require factors based upon the `Authentication`.
This is implemented using xref:servlet/authorization/architecture.adoc#authz-conditional-authorization-manager[`ConditionalAuthorizationManager`].
include-code::./AdminMfaAuthorizationManagerConfiguration[tag=authorizationManager,indent=0]
<1> MFA is required for the user with the username `admin`
<2> Otherwise, MFA is not required
To enable the MFA rules globally, we can publish an `AuthorizationManagerFactory` Bean.
To enable the conditional MFA rules globally, we can publish an `AuthorizationManagerFactory` Bean.
include-code::./AdminMfaAuthorizationManagerConfiguration[tag=authorizationManagerFactory,indent=0]
<1> Inject the custom `AuthorizationManager` as the javadoc:org.springframework.security.authorization.DefaultAuthorizationManagerFactory#setAdditionalAuthorization(org.springframework.security.authorization.AuthorizationManager)[DefaultAuthorization.additionalAuthorization].
This instructs `DefaultAuthorizationManagerFactory` that any authorization rule should apply our custom `AuthorizationManager` along with any authorization requirements defined by the application (e.g. `hasRole("ADMIN")`).
<2> Publish `DefaultAuthorizationManagerFactory` as a Bean, so it is used globally
<1> Require `FACTOR_OTT` and `FACTOR_PASSWORD`
<2> Only apply the requirement if the username is `admin`. Otherwise, MFA is not required.
<3> Return the `AuthorizationManagerFactory` using `.build()`. Since it is published as a Bean, it is used globally.
This should feel very similar to our previous example in xref:./mfa.adoc#authorization-manager-factory[].
The difference is that in the previous example, the `AuthorizationManagerFactories` is setting `DefaultAuthorization.additionalAuthorization` with a built in `AuthorizationManager` that always requires the same authorities.
The difference is that in the previous example, the `AuthorizationManagerFactories` creates an `AuthorizationManager` that always requires the same authorities.
We can now define our authorization rules which are combined with `AdminMfaAuthorizationManager`.
We can now define our authorization rules which are combined with `AuthorizationManagerFactory`.
include-code::./AdminMfaAuthorizationManagerConfiguration[tag=httpSecurity,indent=0]
<1> URLs that begin with `/admin/**` require `ROLE_ADMIN`.
@@ -138,7 +135,7 @@ If we preferred, we could change our logic to enable MFA based upon the roles ra
[[raam-mfa]]
== RequiredAuthoritiesAuthorizationManager
We've demonstrated how we can dynamically determine the authorities for a particular user in xref:./mfa.adoc#programmatic-mfa[] using a custom `AuthorizationManager`.
We've demonstrated how we can dynamically determine the authorities for a particular user in xref:./mfa.adoc#programmatic-mfa[] using `AuthorizationManagerFactories.multiFactor().when`.
However, this is such a common scenario that Spring Security provides built in support using javadoc:org.springframework.security.authorization.RequiredAuthoritiesAuthorizationManager[] and javadoc:org.springframework.security.authorization.RequiredAuthoritiesRepository[].
Let's implement the same requirement that we did in xref:./mfa.adoc#programmatic-mfa[] using the built-in support.
@@ -168,7 +165,7 @@ Our example uses an in memory mapping of usernames to the additional required au
For more dynamic use cases that can be determined by the username, a custom implementation of javadoc:org.springframework.security.authorization.RequiredAuthoritiesRepository[] can be created.
Possible examples would be looking up if a user has enabled MFA in an explicit setting, determining if a user has registered a passkey, etc.
For cases that need to determine MFA based upon the `Authentication`, a custom `AuthorizationManger` can be used as demonstrated in xref:./mfa.adoc#programmatic-mfa[].
For cases that need to determine MFA based upon the `Authentication`, `AuthorizationManagerFactories.multiFactor().when` can be used as demonstrated in xref:./mfa.adoc#programmatic-mfa[].
[[hasallauthorities]]
+1
View File
@@ -6,6 +6,7 @@
* https://github.com/spring-projects/spring-security/pull/18634[gh-18634] - Added javadoc:org.springframework.security.web.util.matcher.InetAddressMatcher[]
* 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]
== OAuth 2.0
@@ -1,17 +1,9 @@
package org.springframework.security.docs.servlet.authentication.programmaticmfa;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authorization.AllAuthoritiesAuthorizationManager;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationManagerFactories;
import org.springframework.security.authorization.AuthorizationManagerFactory;
import org.springframework.security.authorization.AuthorizationResult;
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;
@@ -23,7 +15,6 @@ 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;
import org.springframework.stereotype.Component;
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
@@ -46,36 +37,16 @@ class AdminMfaAuthorizationManagerConfiguration {
}
// end::httpSecurity[]
// tag::authorizationManager[]
@Component
class AdminMfaAuthorizationManager implements AuthorizationManager<Object> {
@Override
public AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication, Object context) {
if ("admin".equals(authentication.get().getName())) {
AuthorizationManager<Object> admins =
AllAuthoritiesAuthorizationManager.hasAllAuthorities(
FactorGrantedAuthority.OTT_AUTHORITY,
FactorGrantedAuthority.PASSWORD_AUTHORITY
);
// <1>
return admins.authorize(authentication, context);
} else {
// <2>
return new AuthorizationDecision(true);
}
}
}
// end::authorizationManager[]
// tag::authorizationManagerFactory[]
@Bean
AuthorizationManagerFactory<Object> authorizationManagerFactory(
AdminMfaAuthorizationManager admins) {
DefaultAuthorizationManagerFactory<Object> defaults = new DefaultAuthorizationManagerFactory<>();
// <1>
defaults.setAdditionalAuthorization(admins);
// <2>
return defaults;
AuthorizationManagerFactory<Object> authorizationManagerFactory() {
// <3>
return AuthorizationManagerFactories.multiFactor()
// <1>
.requireFactors(FactorGrantedAuthority.OTT_AUTHORITY, FactorGrantedAuthority.PASSWORD_AUTHORITY)
// <2>
.when((auth) -> "admin".equals(auth.getName()))
.build();
}
// end::authorizationManagerFactory[]
@@ -14,8 +14,6 @@ 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
import org.springframework.stereotype.Component
import java.util.function.Supplier
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
@@ -40,34 +38,16 @@ internal class AdminMfaAuthorizationManagerConfiguration {
}
// end::httpSecurity[]
// tag::authorizationManager[]
@Component
internal open class AdminMfaAuthorizationManager : AuthorizationManager<Any> {
override fun authorize(
authentication: Supplier<out Authentication?>, context: Any): AuthorizationResult {
return if ("admin" == authentication.get().name) {
var admins =
AllAuthoritiesAuthorizationManager.hasAllAuthorities<Any>(
FactorGrantedAuthority.OTT_AUTHORITY,
FactorGrantedAuthority.PASSWORD_AUTHORITY)
// <1>
admins.authorize(authentication, context)
} else {
// <2>
AuthorizationDecision(true)
}
}
}
// end::authorizationManager[]
// tag::authorizationManagerFactory[]
@Bean
fun authorizationManagerFactory(admins: AdminMfaAuthorizationManager): AuthorizationManagerFactory<Any> {
val defaults = DefaultAuthorizationManagerFactory<Any>()
// <1>
defaults.setAdditionalAuthorization(admins)
// <2>
return defaults
fun authorizationManagerFactory(): AuthorizationManagerFactory<Any> {
// <3>
return AuthorizationManagerFactories.multiFactor<Any>()
// <1>
.requireFactors(FactorGrantedAuthority.OTT_AUTHORITY, FactorGrantedAuthority.PASSWORD_AUTHORITY)
// <2>
.`when` { auth -> "admin" == auth.name }
.build()
}
// end::authorizationManagerFactory[]