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

Create AuthorizationManagerFactories.multiFactor

Closes gh-18032
This commit is contained in:
Rob Winch
2025-10-08 15:09:01 -05:00
parent 488e55032e
commit 702878acae
14 changed files with 138 additions and 140 deletions
@@ -146,8 +146,8 @@ public final class AllRequiredFactorsAuthorizationManager<T> implements Authoriz
* Creates a new {@link Builder}
* @return
*/
public static Builder builder() {
return new Builder();
public static <T> Builder<T> builder() {
return new Builder<>();
}
/**
@@ -156,7 +156,7 @@ public final class AllRequiredFactorsAuthorizationManager<T> implements Authoriz
* @author Rob Winch
* @since 7.0
*/
public static final class Builder {
public static final class Builder<T> {
private List<RequiredFactor> requiredFactors = new ArrayList<>();
@@ -0,0 +1,102 @@
/*
* Copyright 2002-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.authorization;
import java.util.function.Consumer;
/**
* Creates common {@link AuthorizationManagerFactory} instances.
*
* @author Rob Winch
* @since 7.0
* @see DefaultAuthorizationManagerFactory
*/
public final class AuthorizationManagerFactories {
private AuthorizationManagerFactories() {
}
/**
* Creates a {@link AdditionalRequiredFactorsBuilder} that helps build an
* {@link AuthorizationManager} to set on
* {@link DefaultAuthorizationManagerFactory#setAdditionalAuthorization(AuthorizationManager)}
* for multifactor authentication.
* <p>
* Does not affect {@code anonymous}, {@code permitAll}, or {@code denyAll}.
* @param <T> the secured object type
* @return a factory configured with the required authorities
*/
public static <T> AdditionalRequiredFactorsBuilder<T> multiFactor() {
return new AdditionalRequiredFactorsBuilder<>();
}
/**
* A builder that allows creating {@link DefaultAuthorizationManagerFactory} with
* additional requirements for {@link RequiredFactor}s.
*
* @param <T> the type for the {@link DefaultAuthorizationManagerFactory}
* @author Rob Winch
*/
public static final class AdditionalRequiredFactorsBuilder<T> {
private final AllRequiredFactorsAuthorizationManager.Builder<T> factors = AllRequiredFactorsAuthorizationManager
.builder();
/**
* Add additional authorities that will be required.
* @param additionalAuthorities the additional authorities.
* @return the {@link AdditionalRequiredFactorsBuilder} to further customize.
*/
public AdditionalRequiredFactorsBuilder<T> requireFactors(String... additionalAuthorities) {
requireFactors((factors) -> {
for (String authority : additionalAuthorities) {
factors.requireFactor((factor) -> factor.authority(authority));
}
});
return this;
}
public AdditionalRequiredFactorsBuilder<T> requireFactors(
Consumer<AllRequiredFactorsAuthorizationManager.Builder<T>> factors) {
factors.accept(this.factors);
return this;
}
public AdditionalRequiredFactorsBuilder<T> requireFactor(Consumer<RequiredFactor.Builder> factor) {
this.factors.requireFactor(factor);
return this;
}
/**
* Builds a {@link DefaultAuthorizationManagerFactory} that has the
* {@link DefaultAuthorizationManagerFactory#setAdditionalAuthorization(AuthorizationManager)}
* set.
* @return the {@link DefaultAuthorizationManagerFactory}.
*/
public DefaultAuthorizationManagerFactory<T> build() {
DefaultAuthorizationManagerFactory<T> result = new DefaultAuthorizationManagerFactory<>();
AllRequiredFactorsAuthorizationManager<T> additionalChecks = this.factors.build();
result.setAdditionalAuthorization(additionalChecks);
return result;
}
private AdditionalRequiredFactorsBuilder() {
}
}
}
@@ -16,9 +16,6 @@
package org.springframework.security.authorization;
import java.util.ArrayList;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
@@ -26,7 +23,6 @@ import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* A factory for creating different kinds of {@link AuthorizationManager} instances.
@@ -153,18 +149,6 @@ public final class DefaultAuthorizationManagerFactory<T extends @Nullable Object
return createManager(AuthenticatedAuthorizationManager.anonymous());
}
/**
* Creates a {@link Builder} that helps build an {@link AuthorizationManager} to set
* on {@link #setAdditionalAuthorization(AuthorizationManager)} for common scenarios.
* <p>
* Does not affect {@code anonymous}, {@code permitAll}, or {@code denyAll}.
* @param <T> the secured object type
* @return a factory configured with the required authorities
*/
public static <T> Builder<T> builder() {
return new Builder<>();
}
private AuthorizationManager<T> createManager(AuthorityAuthorizationManager<T> authorizationManager) {
authorizationManager.setRoleHierarchy(this.roleHierarchy);
return withAdditionalAuthorization(authorizationManager);
@@ -187,63 +171,4 @@ public final class DefaultAuthorizationManagerFactory<T extends @Nullable Object
return AuthorizationManagers.allOf(new AuthorizationDecision(false), this.additionalAuthorization, manager);
}
/**
* A builder that allows creating {@link DefaultAuthorizationManagerFactory} with
* additional authorization for common scenarios.
*
* @param <T> the type for the {@link DefaultAuthorizationManagerFactory}
* @author Rob Winch
*/
public static final class Builder<T> {
private final List<String> additionalAuthorities = new ArrayList<>();
private RoleHierarchy roleHierarchy = new NullRoleHierarchy();
/**
* Add additional authorities that will be required.
* @param additionalAuthorities the additional authorities.
* @return the {@link Builder} to further customize.
*/
public Builder<T> requireAdditionalAuthorities(String... additionalAuthorities) {
Assert.notEmpty(additionalAuthorities, "additionalAuthorities cannot be empty");
for (String additionalAuthority : additionalAuthorities) {
this.additionalAuthorities.add(additionalAuthority);
}
return this;
}
/**
* The {@link RoleHierarchy} to use.
* @param roleHierarchy the non-null {@link RoleHierarchy} to use. Default is
* {@link NullRoleHierarchy}.
* @return the Builder to further customize.
*/
public Builder<T> roleHierarchy(RoleHierarchy roleHierarchy) {
Assert.notNull(roleHierarchy, "roleHierarchy cannot be null");
this.roleHierarchy = roleHierarchy;
return this;
}
/**
* Builds a {@link DefaultAuthorizationManagerFactory} that has the
* {@link #setAdditionalAuthorization(AuthorizationManager)} set.
* @return the {@link DefaultAuthorizationManagerFactory}.
*/
public DefaultAuthorizationManagerFactory<T> build() {
Assert.state(!CollectionUtils.isEmpty(this.additionalAuthorities), "additionalAuthorities cannot be empty");
DefaultAuthorizationManagerFactory<T> result = new DefaultAuthorizationManagerFactory<>();
AllAuthoritiesAuthorizationManager<T> additionalChecks = AllAuthoritiesAuthorizationManager
.hasAllAuthorities(this.additionalAuthorities);
result.setRoleHierarchy(this.roleHierarchy);
additionalChecks.setRoleHierarchy(this.roleHierarchy);
result.setAdditionalAuthorization(additionalChecks);
return result;
}
private Builder() {
}
}
}
@@ -30,6 +30,7 @@ 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.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Test {@link AllRequiredFactorsAuthorizationManager}.
@@ -228,7 +229,7 @@ class AllRequiredFactorsAuthorizationManagerTests {
@Test
void builderBuildWhenEmpty() {
assertThatIllegalArgumentException().isThrownBy(() -> AllRequiredFactorsAuthorizationManager.builder().build());
assertThatIllegalStateException().isThrownBy(() -> AllRequiredFactorsAuthorizationManager.builder().build());
}
@Test
@@ -16,16 +16,11 @@
package org.springframework.security.authorization;
import java.util.Collection;
import org.junit.jupiter.api.Test;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.core.authority.AuthorityUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
@@ -291,14 +286,15 @@ public class AuthorizationManagerFactoryTests {
@Test
public void builderWhenEmptyAdditionalAuthoritiesThenIllegalStateException() {
DefaultAuthorizationManagerFactory.Builder<Object> builder = DefaultAuthorizationManagerFactory.builder();
AuthorizationManagerFactories.AdditionalRequiredFactorsBuilder<Object> builder = AuthorizationManagerFactories
.multiFactor();
assertThatIllegalStateException().isThrownBy(() -> builder.build());
}
@Test
public void builderWhenAdditionalAuthorityThenRequired() {
AuthorizationManagerFactory<String> factory = DefaultAuthorizationManagerFactory.<String>builder()
.requireAdditionalAuthorities("ROLE_ADMIN")
AuthorizationManagerFactory<String> factory = AuthorizationManagerFactories.<String>multiFactor()
.requireFactors("ROLE_ADMIN")
.build();
assertUserDenied(factory.hasRole("USER"));
assertThat(factory.hasRole("USER").authorize(() -> TestAuthentication.authenticatedAdmin(), "").isGranted())
@@ -307,42 +303,14 @@ public class AuthorizationManagerFactoryTests {
@Test
public void builderWhenAdditionalAuthoritiesThenRequired() {
AuthorizationManagerFactory<String> factory = DefaultAuthorizationManagerFactory.<String>builder()
.requireAdditionalAuthorities("ROLE_ADMIN", "ROLE_USER")
AuthorizationManagerFactory<String> factory = AuthorizationManagerFactories.<String>multiFactor()
.requireFactors("ROLE_ADMIN", "ROLE_USER")
.build();
assertUserDenied(factory.hasRole("USER"));
assertThat(factory.hasRole("USER").authorize(() -> TestAuthentication.authenticatedAdmin(), "").isGranted())
.isTrue();
}
@Test
public void builderWhenNullRoleHierachyThenIllegalArgumentException() {
DefaultAuthorizationManagerFactory.Builder<Object> builder = DefaultAuthorizationManagerFactory.builder();
assertThatIllegalArgumentException().isThrownBy(() -> builder.roleHierarchy(null));
}
@Test
public void builderWhenRoleHierarchyThenUsed() {
RoleHierarchy roleHierarchy = mock(RoleHierarchy.class);
String ROLE_HIERARCHY = "ROLE_HIERARCHY";
Collection authorityHierarchy = AuthorityUtils.createAuthorityList(ROLE_HIERARCHY, "ROLE_USER");
given(roleHierarchy.getReachableGrantedAuthorities(any())).willReturn(authorityHierarchy);
DefaultAuthorizationManagerFactory<String> factory = DefaultAuthorizationManagerFactory.<String>builder()
.requireAdditionalAuthorities(ROLE_HIERARCHY)
.roleHierarchy(roleHierarchy)
.build();
// ROLE_USER is replaced with the RoleHierarchy (ROLE_USER, ROLE_HIERARCHY)
assertUserGranted(factory.hasAuthority("ROLE_USER"));
// ROLE_ADMIN is replaced with the RoleHierarchy (ROLE_USER, ROLE_HIERARCHY)
assertThat(factory.hasAuthority("ROLE_ADMIN")
.authorize(() -> TestAuthentication.authenticatedAdmin(), "")
.isGranted()).isFalse();
verify(roleHierarchy, times(4)).getReachableGrantedAuthorities(any());
}
private void assertUserGranted(AuthorizationManager<String> manager) {
assertThat(manager.authorize(() -> TestAuthentication.authenticatedUser(), "").isGranted()).isTrue();
}