Browse Source

Add AuthorizationManagerFactory

Signed-off-by: Steve Riesenberg <5248162+sjohnr@users.noreply.github.com>
Steve Riesenberg 2 weeks ago
parent
commit
eeb4574bb3
37 changed files with 2718 additions and 177 deletions
  1. 6 0
      config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java
  2. 57 35
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java
  3. 322 0
      config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java
  4. 53 1
      core/src/main/java/org/springframework/security/access/expression/AbstractSecurityExpressionHandler.java
  5. 113 64
      core/src/main/java/org/springframework/security/access/expression/SecurityExpressionRoot.java
  6. 29 6
      core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java
  7. 7 7
      core/src/main/java/org/springframework/security/access/expression/method/MethodSecurityExpressionRoot.java
  8. 124 0
      core/src/main/java/org/springframework/security/authorization/AuthorizationManagerFactory.java
  9. 123 0
      core/src/main/java/org/springframework/security/authorization/DefaultAuthorizationManagerFactory.java
  10. 2 1
      core/src/test/java/org/springframework/security/access/expression/method/MethodSecurityExpressionRootTests.java
  11. 100 0
      core/src/test/java/org/springframework/security/authorization/AuthorizationManagerFactoryTests.java
  12. 53 11
      data/src/main/java/org/springframework/security/data/repository/query/SecurityEvaluationContextExtension.java
  13. 16 15
      data/src/test/java/org/springframework/security/data/repository/query/SecurityEvaluationContextExtensionTests.java
  14. 37 0
      docs/modules/ROOT/pages/servlet/authorization/architecture.adoc
  15. 19 0
      docs/modules/ROOT/pages/servlet/authorization/authorize-http-requests.adoc
  16. 18 0
      docs/modules/ROOT/pages/servlet/authorization/method-security.adoc
  17. 1 0
      docs/modules/ROOT/pages/whats-new.adoc
  18. 77 0
      docs/src/test/java/org/springframework/security/docs/servlet/authorization/authzauthorizationmanagerfactory/AuthorizationManagerFactoryConfiguration.java
  19. 208 0
      docs/src/test/java/org/springframework/security/docs/servlet/authorization/authzauthorizationmanagerfactory/AuthorizationManagerFactoryConfigurationTests.java
  20. 48 0
      docs/src/test/java/org/springframework/security/docs/servlet/authorization/customizingauthorizationmanagers/CustomHttpRequestsAuthorizationManagerFactory.java
  21. 137 0
      docs/src/test/java/org/springframework/security/docs/servlet/authorization/customizingauthorizationmanagers/CustomHttpRequestsAuthorizationManagerFactoryTests.java
  22. 57 0
      docs/src/test/java/org/springframework/security/docs/servlet/authorization/customizingauthorizationmanagers/CustomMethodInvocationAuthorizationManagerFactory.java
  23. 191 0
      docs/src/test/java/org/springframework/security/docs/servlet/authorization/customizingauthorizationmanagers/CustomMethodInvocationAuthorizationManagerFactoryTests.java
  24. 62 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authorization/authzauthorizationmanagerfactory/AuthorizationManagerFactoryConfiguration.kt
  25. 229 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authorization/authzauthorizationmanagerfactory/AuthorizationManagerFactoryConfigurationTests.kt
  26. 44 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authorization/customizingauthorizationmanagers/CustomHttpRequestsAuthorizationManagerFactory.kt
  27. 131 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authorization/customizingauthorizationmanagers/CustomHttpRequestsAuthorizationManagerFactoryTests.kt
  28. 50 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authorization/customizingauthorizationmanagers/CustomMethodInvocationAuthorizationManagerFactory.kt
  29. 215 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authorization/customizingauthorizationmanagers/CustomMethodInvocationAuthorizationManagerFactoryTests.kt
  30. 40 0
      docs/src/test/resources/org/springframework/security/docs/servlet/authorization/authzauthorizationmanagerfactory/AuthorizationManagerFactoryConfiguration.xml
  31. 1 0
      etc/checkstyle/checkstyle-suppressions.xml
  32. 11 11
      messaging/src/main/java/org/springframework/security/messaging/access/expression/DefaultMessageSecurityExpressionHandler.java
  33. 9 5
      messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageSecurityExpressionRoot.java
  34. 18 8
      web/src/main/java/org/springframework/security/web/access/expression/DefaultHttpSecurityExpressionHandler.java
  35. 22 10
      web/src/main/java/org/springframework/security/web/access/expression/DefaultWebSecurityExpressionHandler.java
  36. 62 0
      web/src/main/java/org/springframework/security/web/access/expression/FilterInvocationExpressionRoot.java
  37. 26 3
      web/src/main/java/org/springframework/security/web/access/expression/WebSecurityExpressionRoot.java

+ 6 - 0
config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java

@@ -39,6 +39,7 @@ import org.springframework.security.aot.hint.PrePostAuthorizeHintsRegistrar;
 import org.springframework.security.aot.hint.SecurityHintsRegistrar;
 import org.springframework.security.aot.hint.SecurityHintsRegistrar;
 import org.springframework.security.authorization.AuthorizationEventPublisher;
 import org.springframework.security.authorization.AuthorizationEventPublisher;
 import org.springframework.security.authorization.AuthorizationManager;
 import org.springframework.security.authorization.AuthorizationManager;
+import org.springframework.security.authorization.AuthorizationManagerFactory;
 import org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor;
 import org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor;
 import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
 import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
 import org.springframework.security.authorization.method.MethodInvocationResult;
 import org.springframework.security.authorization.method.MethodInvocationResult;
@@ -121,6 +122,11 @@ final class PrePostMethodSecurityConfiguration implements ImportAware, Applicati
 		this.expressionHandler.setRoleHierarchy(roleHierarchy);
 		this.expressionHandler.setRoleHierarchy(roleHierarchy);
 	}
 	}
 
 
+	@Autowired(required = false)
+	void setAuthorizationManagerFactory(AuthorizationManagerFactory<MethodInvocation> authorizationManagerFactory) {
+		this.expressionHandler.setAuthorizationManagerFactory(authorizationManagerFactory);
+	}
+
 	@Autowired(required = false)
 	@Autowired(required = false)
 	void setTemplateDefaults(AnnotationTemplateExpressionDefaults templateDefaults) {
 	void setTemplateDefaults(AnnotationTemplateExpressionDefaults templateDefaults) {
 		this.preFilterMethodInterceptor.setTemplateDefaults(templateDefaults);
 		this.preFilterMethodInterceptor.setTemplateDefaults(templateDefaults);

+ 57 - 35
config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java

@@ -18,7 +18,6 @@ package org.springframework.security.config.annotation.web.configurers;
 
 
 import java.util.List;
 import java.util.List;
 import java.util.function.Function;
 import java.util.function.Function;
-import java.util.function.Supplier;
 
 
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletRequest;
 
 
@@ -27,13 +26,12 @@ import org.springframework.context.ApplicationContext;
 import org.springframework.core.ResolvableType;
 import org.springframework.core.ResolvableType;
 import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
 import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
 import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
 import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
-import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
-import org.springframework.security.authorization.AuthorityAuthorizationManager;
 import org.springframework.security.authorization.AuthorizationDecision;
 import org.springframework.security.authorization.AuthorizationDecision;
 import org.springframework.security.authorization.AuthorizationEventPublisher;
 import org.springframework.security.authorization.AuthorizationEventPublisher;
 import org.springframework.security.authorization.AuthorizationManager;
 import org.springframework.security.authorization.AuthorizationManager;
+import org.springframework.security.authorization.AuthorizationManagerFactory;
 import org.springframework.security.authorization.AuthorizationManagers;
 import org.springframework.security.authorization.AuthorizationManagers;
-import org.springframework.security.authorization.SingleResultAuthorizationManager;
+import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
 import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
 import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
 import org.springframework.security.config.ObjectPostProcessor;
 import org.springframework.security.config.ObjectPostProcessor;
 import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
 import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
@@ -46,13 +44,13 @@ import org.springframework.security.web.access.intercept.RequestMatcherDelegatin
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcherEntry;
 import org.springframework.security.web.util.matcher.RequestMatcherEntry;
 import org.springframework.util.Assert;
 import org.springframework.util.Assert;
-import org.springframework.util.function.SingletonSupplier;
 
 
 /**
 /**
  * Adds a URL based authorization using {@link AuthorizationManager}.
  * Adds a URL based authorization using {@link AuthorizationManager}.
  *
  *
  * @param <H> the type of {@link HttpSecurityBuilder} that is being configured.
  * @param <H> the type of {@link HttpSecurityBuilder} that is being configured.
  * @author Evgeniy Cheban
  * @author Evgeniy Cheban
+ * @author Steve Riesenberg
  * @since 5.5
  * @since 5.5
  */
  */
 public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder<H>>
 public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder<H>>
@@ -62,9 +60,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 
 
 	private final AuthorizationEventPublisher publisher;
 	private final AuthorizationEventPublisher publisher;
 
 
-	private final Supplier<RoleHierarchy> roleHierarchy;
-
-	private String rolePrefix = "ROLE_";
+	private final AuthorizationManagerFactory<RequestAuthorizationContext> authorizationManagerFactory;
 
 
 	private ObjectPostProcessor<AuthorizationManager<HttpServletRequest>> postProcessor = ObjectPostProcessor
 	private ObjectPostProcessor<AuthorizationManager<HttpServletRequest>> postProcessor = ObjectPostProcessor
 		.identity();
 		.identity();
@@ -81,13 +77,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 		else {
 		else {
 			this.publisher = new SpringAuthorizationEventPublisher(context);
 			this.publisher = new SpringAuthorizationEventPublisher(context);
 		}
 		}
-		this.roleHierarchy = SingletonSupplier.of(() -> (context.getBeanNamesForType(RoleHierarchy.class).length > 0)
-				? context.getBean(RoleHierarchy.class) : new NullRoleHierarchy());
-		String[] grantedAuthorityDefaultsBeanNames = context.getBeanNamesForType(GrantedAuthorityDefaults.class);
-		if (grantedAuthorityDefaultsBeanNames.length > 0) {
-			GrantedAuthorityDefaults grantedAuthorityDefaults = context.getBean(GrantedAuthorityDefaults.class);
-			this.rolePrefix = grantedAuthorityDefaults.getRolePrefix();
-		}
+		this.authorizationManagerFactory = getAuthorizationManagerFactory(context);
 		ResolvableType type = ResolvableType.forClassWithGenerics(ObjectPostProcessor.class,
 		ResolvableType type = ResolvableType.forClassWithGenerics(ObjectPostProcessor.class,
 				ResolvableType.forClassWithGenerics(AuthorizationManager.class, HttpServletRequest.class));
 				ResolvableType.forClassWithGenerics(AuthorizationManager.class, HttpServletRequest.class));
 		ObjectProvider<ObjectPostProcessor<AuthorizationManager<HttpServletRequest>>> provider = context
 		ObjectProvider<ObjectPostProcessor<AuthorizationManager<HttpServletRequest>>> provider = context
@@ -95,6 +85,35 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 		provider.ifUnique((postProcessor) -> this.postProcessor = postProcessor);
 		provider.ifUnique((postProcessor) -> this.postProcessor = postProcessor);
 	}
 	}
 
 
+	private AuthorizationManagerFactory<RequestAuthorizationContext> getAuthorizationManagerFactory(
+			ApplicationContext context) {
+		ResolvableType authorizationManagerFactoryType = ResolvableType
+			.forClassWithGenerics(AuthorizationManagerFactory.class, RequestAuthorizationContext.class);
+
+		// Handle fallback to generic type
+		if (context.getBeanNamesForType(authorizationManagerFactoryType).length == 0) {
+			authorizationManagerFactoryType = ResolvableType.forClassWithGenerics(AuthorizationManagerFactory.class,
+					Object.class);
+		}
+
+		ObjectProvider<AuthorizationManagerFactory<RequestAuthorizationContext>> authorizationManagerFactoryProvider = context
+			.getBeanProvider(authorizationManagerFactoryType);
+
+		return authorizationManagerFactoryProvider.getIfAvailable(() -> {
+			RoleHierarchy roleHierarchy = context.getBeanProvider(RoleHierarchy.class)
+				.getIfAvailable(NullRoleHierarchy::new);
+			GrantedAuthorityDefaults grantedAuthorityDefaults = context.getBeanProvider(GrantedAuthorityDefaults.class)
+				.getIfAvailable();
+			String rolePrefix = (grantedAuthorityDefaults != null) ? grantedAuthorityDefaults.getRolePrefix() : "ROLE_";
+
+			DefaultAuthorizationManagerFactory<RequestAuthorizationContext> authorizationManagerFactory = new DefaultAuthorizationManagerFactory<>();
+			authorizationManagerFactory.setRoleHierarchy(roleHierarchy);
+			authorizationManagerFactory.setRolePrefix(rolePrefix);
+
+			return authorizationManagerFactory;
+		});
+	}
+
 	/**
 	/**
 	 * The {@link AuthorizationManagerRequestMatcherRegistry} is what users will interact
 	 * The {@link AuthorizationManagerRequestMatcherRegistry} is what users will interact
 	 * with after applying the {@link AuthorizeHttpRequestsConfigurer}.
 	 * with after applying the {@link AuthorizeHttpRequestsConfigurer}.
@@ -173,7 +192,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 		@Override
 		@Override
 		protected AuthorizedUrl chainRequestMatchers(List<RequestMatcher> requestMatchers) {
 		protected AuthorizedUrl chainRequestMatchers(List<RequestMatcher> requestMatchers) {
 			this.unmappedMatchers = requestMatchers;
 			this.unmappedMatchers = requestMatchers;
-			return new AuthorizedUrl(requestMatchers);
+			return new AuthorizedUrl(requestMatchers, AuthorizeHttpRequestsConfigurer.this.authorizationManagerFactory);
 		}
 		}
 
 
 		/**
 		/**
@@ -201,20 +220,31 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 
 
 		private final List<? extends RequestMatcher> matchers;
 		private final List<? extends RequestMatcher> matchers;
 
 
+		private AuthorizationManagerFactory<RequestAuthorizationContext> authorizationManagerFactory;
+
 		private boolean not;
 		private boolean not;
 
 
 		/**
 		/**
 		 * Creates an instance.
 		 * Creates an instance.
 		 * @param matchers the {@link RequestMatcher} instances to map
 		 * @param matchers the {@link RequestMatcher} instances to map
+		 * @param authorizationManagerFactory the {@link AuthorizationManagerFactory} for
+		 * creating instances of {@link AuthorizationManager}
 		 */
 		 */
-		AuthorizedUrl(List<? extends RequestMatcher> matchers) {
+		AuthorizedUrl(List<? extends RequestMatcher> matchers,
+				AuthorizationManagerFactory<RequestAuthorizationContext> authorizationManagerFactory) {
 			this.matchers = matchers;
 			this.matchers = matchers;
+			this.authorizationManagerFactory = authorizationManagerFactory;
 		}
 		}
 
 
 		protected List<? extends RequestMatcher> getMatchers() {
 		protected List<? extends RequestMatcher> getMatchers() {
 			return this.matchers;
 			return this.matchers;
 		}
 		}
 
 
+		void setAuthorizationManagerFactory(
+				AuthorizationManagerFactory<RequestAuthorizationContext> authorizationManagerFactory) {
+			this.authorizationManagerFactory = authorizationManagerFactory;
+		}
+
 		/**
 		/**
 		 * Negates the following authorization rule.
 		 * Negates the following authorization rule.
 		 * @return the {@link AuthorizedUrl} for further customization
 		 * @return the {@link AuthorizedUrl} for further customization
@@ -231,7 +261,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 		 * customizations
 		 * customizations
 		 */
 		 */
 		public AuthorizationManagerRequestMatcherRegistry permitAll() {
 		public AuthorizationManagerRequestMatcherRegistry permitAll() {
-			return access(SingleResultAuthorizationManager.permitAll());
+			return access(this.authorizationManagerFactory.permitAll());
 		}
 		}
 
 
 		/**
 		/**
@@ -240,7 +270,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 		 * customizations
 		 * customizations
 		 */
 		 */
 		public AuthorizationManagerRequestMatcherRegistry denyAll() {
 		public AuthorizationManagerRequestMatcherRegistry denyAll() {
-			return access(SingleResultAuthorizationManager.denyAll());
+			return access(this.authorizationManagerFactory.denyAll());
 		}
 		}
 
 
 		/**
 		/**
@@ -251,8 +281,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 		 * customizations
 		 * customizations
 		 */
 		 */
 		public AuthorizationManagerRequestMatcherRegistry hasRole(String role) {
 		public AuthorizationManagerRequestMatcherRegistry hasRole(String role) {
-			return access(withRoleHierarchy(AuthorityAuthorizationManager
-				.hasAnyRole(AuthorizeHttpRequestsConfigurer.this.rolePrefix, new String[] { role })));
+			return access(this.authorizationManagerFactory.hasRole(role));
 		}
 		}
 
 
 		/**
 		/**
@@ -264,8 +293,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 		 * customizations
 		 * customizations
 		 */
 		 */
 		public AuthorizationManagerRequestMatcherRegistry hasAnyRole(String... roles) {
 		public AuthorizationManagerRequestMatcherRegistry hasAnyRole(String... roles) {
-			return access(withRoleHierarchy(
-					AuthorityAuthorizationManager.hasAnyRole(AuthorizeHttpRequestsConfigurer.this.rolePrefix, roles)));
+			return access(this.authorizationManagerFactory.hasAnyRole(roles));
 		}
 		}
 
 
 		/**
 		/**
@@ -275,7 +303,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 		 * customizations
 		 * customizations
 		 */
 		 */
 		public AuthorizationManagerRequestMatcherRegistry hasAuthority(String authority) {
 		public AuthorizationManagerRequestMatcherRegistry hasAuthority(String authority) {
-			return access(withRoleHierarchy(AuthorityAuthorizationManager.hasAuthority(authority)));
+			return access(this.authorizationManagerFactory.hasAuthority(authority));
 		}
 		}
 
 
 		/**
 		/**
@@ -286,13 +314,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 		 * customizations
 		 * customizations
 		 */
 		 */
 		public AuthorizationManagerRequestMatcherRegistry hasAnyAuthority(String... authorities) {
 		public AuthorizationManagerRequestMatcherRegistry hasAnyAuthority(String... authorities) {
-			return access(withRoleHierarchy(AuthorityAuthorizationManager.hasAnyAuthority(authorities)));
-		}
-
-		private AuthorityAuthorizationManager<RequestAuthorizationContext> withRoleHierarchy(
-				AuthorityAuthorizationManager<RequestAuthorizationContext> manager) {
-			manager.setRoleHierarchy(AuthorizeHttpRequestsConfigurer.this.roleHierarchy.get());
-			return manager;
+			return access(this.authorizationManagerFactory.hasAnyAuthority(authorities));
 		}
 		}
 
 
 		/**
 		/**
@@ -301,7 +323,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 		 * customizations
 		 * customizations
 		 */
 		 */
 		public AuthorizationManagerRequestMatcherRegistry authenticated() {
 		public AuthorizationManagerRequestMatcherRegistry authenticated() {
-			return access(AuthenticatedAuthorizationManager.authenticated());
+			return access(this.authorizationManagerFactory.authenticated());
 		}
 		}
 
 
 		/**
 		/**
@@ -313,7 +335,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 		 * @see RememberMeConfigurer
 		 * @see RememberMeConfigurer
 		 */
 		 */
 		public AuthorizationManagerRequestMatcherRegistry fullyAuthenticated() {
 		public AuthorizationManagerRequestMatcherRegistry fullyAuthenticated() {
-			return access(AuthenticatedAuthorizationManager.fullyAuthenticated());
+			return access(this.authorizationManagerFactory.fullyAuthenticated());
 		}
 		}
 
 
 		/**
 		/**
@@ -324,7 +346,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 		 * @see RememberMeConfigurer
 		 * @see RememberMeConfigurer
 		 */
 		 */
 		public AuthorizationManagerRequestMatcherRegistry rememberMe() {
 		public AuthorizationManagerRequestMatcherRegistry rememberMe() {
-			return access(AuthenticatedAuthorizationManager.rememberMe());
+			return access(this.authorizationManagerFactory.rememberMe());
 		}
 		}
 
 
 		/**
 		/**
@@ -334,7 +356,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 		 * @since 5.8
 		 * @since 5.8
 		 */
 		 */
 		public AuthorizationManagerRequestMatcherRegistry anonymous() {
 		public AuthorizationManagerRequestMatcherRegistry anonymous() {
-			return access(AuthenticatedAuthorizationManager.anonymous());
+			return access(this.authorizationManagerFactory.anonymous());
 		}
 		}
 
 
 		/**
 		/**

+ 322 - 0
config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java

@@ -16,6 +16,7 @@
 
 
 package org.springframework.security.config.annotation.web.configurers;
 package org.springframework.security.config.annotation.web.configurers;
 
 
+import java.util.Set;
 import java.util.function.Supplier;
 import java.util.function.Supplier;
 
 
 import io.micrometer.observation.Observation;
 import io.micrometer.observation.Observation;
@@ -36,14 +37,19 @@ import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.event.EventListener;
 import org.springframework.context.event.EventListener;
+import org.springframework.http.HttpMethod;
 import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
 import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
 import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
 import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
 import org.springframework.security.authentication.RememberMeAuthenticationToken;
 import org.springframework.security.authentication.RememberMeAuthenticationToken;
 import org.springframework.security.authentication.TestAuthentication;
 import org.springframework.security.authentication.TestAuthentication;
+import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
+import org.springframework.security.authorization.AuthorityAuthorizationManager;
 import org.springframework.security.authorization.AuthorizationDecision;
 import org.springframework.security.authorization.AuthorizationDecision;
 import org.springframework.security.authorization.AuthorizationEventPublisher;
 import org.springframework.security.authorization.AuthorizationEventPublisher;
 import org.springframework.security.authorization.AuthorizationManager;
 import org.springframework.security.authorization.AuthorizationManager;
+import org.springframework.security.authorization.AuthorizationManagerFactory;
 import org.springframework.security.authorization.AuthorizationObservationContext;
 import org.springframework.security.authorization.AuthorizationObservationContext;
+import org.springframework.security.authorization.SingleResultAuthorizationManager;
 import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
 import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
 import org.springframework.security.authorization.event.AuthorizationDeniedEvent;
 import org.springframework.security.authorization.event.AuthorizationDeniedEvent;
 import org.springframework.security.config.ObjectPostProcessor;
 import org.springframework.security.config.ObjectPostProcessor;
@@ -82,13 +88,17 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc;
 
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
 import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.BDDMockito.given;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.springframework.security.config.Customizer.withDefaults;
 import static org.springframework.security.config.Customizer.withDefaults;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous;
 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
@@ -170,6 +180,26 @@ public class AuthorizeHttpRequestsConfigurerTests {
 			.withMessageContaining("manager cannot be null");
 			.withMessageContaining("manager cannot be null");
 	}
 	}
 
 
+	@Test
+	public void configureWhenCustomAuthorizationManagerFactoryRegisteredThenUsed() {
+		AuthorizationManager<RequestAuthorizationContext> authorizationManager = mock();
+		AuthorizationManagerFactory<RequestAuthorizationContext> authorizationManagerFactory = mockAuthorizationManagerFactory(
+				authorizationManager);
+		AuthorizationManagerFactoryConfig.authorizationManagerFactory = authorizationManagerFactory;
+		this.spring.register(AuthorizationManagerFactoryConfig.class).autowire();
+		verify(authorizationManagerFactory).permitAll();
+		verify(authorizationManagerFactory).denyAll();
+		verify(authorizationManagerFactory).hasRole("ADMIN");
+		verify(authorizationManagerFactory).hasAnyRole("USER", "ADMIN");
+		verify(authorizationManagerFactory).hasAuthority("write");
+		verify(authorizationManagerFactory).hasAnyAuthority("resource.read", "read");
+		verify(authorizationManagerFactory).authenticated();
+		verify(authorizationManagerFactory).fullyAuthenticated();
+		verify(authorizationManagerFactory).rememberMe();
+		verify(authorizationManagerFactory).anonymous();
+		verifyNoMoreInteractions(authorizationManagerFactory);
+	}
+
 	@Test
 	@Test
 	public void configureWhenObjectPostProcessorRegisteredThenInvokedOnAuthorizationManagerAndAuthorizationFilter() {
 	public void configureWhenObjectPostProcessorRegisteredThenInvokedOnAuthorizationManagerAndAuthorizationFilter() {
 		this.spring.register(ObjectPostProcessorConfig.class).autowire();
 		this.spring.register(ObjectPostProcessorConfig.class).autowire();
@@ -538,6 +568,205 @@ public class AuthorizeHttpRequestsConfigurerTests {
 		this.mvc.perform(requestWithAdmin).andExpect(status().isOk());
 		this.mvc.perform(requestWithAdmin).andExpect(status().isOk());
 	}
 	}
 
 
+	@Test
+	public void getWhenCustomAuthorizationManagerFactoryRegisteredAndPermitAllThenRespondsWithOk() throws Exception {
+		AuthorizationManager<RequestAuthorizationContext> authorizationManager = mock();
+		AuthorizationManagerFactory<RequestAuthorizationContext> authorizationManagerFactory = mockAuthorizationManagerFactory(
+				authorizationManager);
+		AuthorizationManager<RequestAuthorizationContext> permitAll = spy(SingleResultAuthorizationManager.permitAll());
+		given(authorizationManagerFactory.permitAll()).willReturn(permitAll);
+		AuthorizationManagerFactoryConfig.authorizationManagerFactory = authorizationManagerFactory;
+
+		this.spring.register(AuthorizationManagerFactoryConfig.class, AccessTestController.class).autowire();
+		MockHttpServletRequestBuilder request = get("/public").with(anonymous());
+		this.mvc.perform(request).andExpect(status().isOk());
+		verify(permitAll).authorize(any(), any(RequestAuthorizationContext.class));
+		verifyNoMoreInteractions(permitAll);
+		verifyNoInteractions(authorizationManager);
+	}
+
+	@Test
+	public void getWhenCustomAuthorizationManagerFactoryRegisteredAndDenyAllThenRespondsWithForbidden()
+			throws Exception {
+		AuthorizationManager<RequestAuthorizationContext> authorizationManager = mock();
+		AuthorizationManagerFactory<RequestAuthorizationContext> authorizationManagerFactory = mockAuthorizationManagerFactory(
+				authorizationManager);
+		AuthorizationManager<RequestAuthorizationContext> denyAll = spy(SingleResultAuthorizationManager.denyAll());
+		given(authorizationManagerFactory.denyAll()).willReturn(denyAll);
+		AuthorizationManagerFactoryConfig.authorizationManagerFactory = authorizationManagerFactory;
+
+		this.spring.register(AuthorizationManagerFactoryConfig.class, AccessTestController.class).autowire();
+		MockHttpServletRequestBuilder request = get("/private").with(user("user"));
+		this.mvc.perform(request).andExpect(status().isForbidden());
+		verify(denyAll).authorize(any(), any(RequestAuthorizationContext.class));
+		verifyNoMoreInteractions(denyAll);
+		verifyNoInteractions(authorizationManager);
+	}
+
+	@Test
+	public void getWhenCustomAuthorizationManagerFactoryRegisteredAndHasRoleThenRespondsWithOk() throws Exception {
+		AuthorizationManager<RequestAuthorizationContext> authorizationManager = mock();
+		AuthorizationManagerFactory<RequestAuthorizationContext> authorizationManagerFactory = mockAuthorizationManagerFactory(
+				authorizationManager);
+		AuthorizationManager<RequestAuthorizationContext> hasRole = spy(AuthorityAuthorizationManager.hasRole("ADMIN"));
+		given(authorizationManagerFactory.hasRole(anyString())).willReturn(hasRole);
+		AuthorizationManagerFactoryConfig.authorizationManagerFactory = authorizationManagerFactory;
+
+		this.spring.register(AuthorizationManagerFactoryConfig.class, AccessTestController.class).autowire();
+		MockHttpServletRequestBuilder request = get("/admin").with(user("admin").roles("ADMIN"));
+		this.mvc.perform(request).andExpect(status().isOk());
+		verify(hasRole).authorize(any(), any(RequestAuthorizationContext.class));
+		verifyNoMoreInteractions(hasRole);
+		verifyNoInteractions(authorizationManager);
+	}
+
+	@Test
+	public void getWhenCustomAuthorizationManagerFactoryRegisteredAndHasAnyRoleThenRespondsWithOk() throws Exception {
+		AuthorizationManager<RequestAuthorizationContext> authorizationManager = mock();
+		AuthorizationManagerFactory<RequestAuthorizationContext> authorizationManagerFactory = mockAuthorizationManagerFactory(
+				authorizationManager);
+		AuthorizationManager<RequestAuthorizationContext> hasAnyRole = spy(
+				AuthorityAuthorizationManager.hasAnyRole("USER", "ADMIN"));
+		given(authorizationManagerFactory.hasAnyRole(any(String[].class))).willReturn(hasAnyRole);
+		AuthorizationManagerFactoryConfig.authorizationManagerFactory = authorizationManagerFactory;
+
+		this.spring.register(AuthorizationManagerFactoryConfig.class, AccessTestController.class).autowire();
+		MockHttpServletRequestBuilder request = get("/user").with(user("user").roles("USER"));
+		this.mvc.perform(request).andExpect(status().isOk());
+		verify(hasAnyRole).authorize(any(), any(RequestAuthorizationContext.class));
+		verifyNoMoreInteractions(hasAnyRole);
+		verifyNoInteractions(authorizationManager);
+	}
+
+	@Test
+	public void postWhenCustomAuthorizationManagerFactoryRegisteredAndHasAuthorityThenRespondsWithOk()
+			throws Exception {
+		AuthorizationManager<RequestAuthorizationContext> authorizationManager = mock();
+		AuthorizationManagerFactory<RequestAuthorizationContext> authorizationManagerFactory = mockAuthorizationManagerFactory(
+				authorizationManager);
+		AuthorizationManager<RequestAuthorizationContext> hasAuthority = spy(
+				AuthorityAuthorizationManager.hasAuthority("write"));
+		given(authorizationManagerFactory.hasAuthority(anyString())).willReturn(hasAuthority);
+		AuthorizationManagerFactoryConfig.authorizationManagerFactory = authorizationManagerFactory;
+
+		this.spring.register(AuthorizationManagerFactoryConfig.class, AccessTestController.class).autowire();
+		MockHttpServletRequestBuilder request = post("/resource")
+			.with(user("user").authorities(new SimpleGrantedAuthority("write")))
+			.with(csrf());
+		this.mvc.perform(request).andExpect(status().isOk());
+		verify(hasAuthority).authorize(any(), any(RequestAuthorizationContext.class));
+		verifyNoMoreInteractions(hasAuthority);
+		verifyNoInteractions(authorizationManager);
+	}
+
+	@Test
+	public void getWhenCustomAuthorizationManagerFactoryRegisteredAndHasAnyAuthorityThenRespondsWithOk()
+			throws Exception {
+		AuthorizationManager<RequestAuthorizationContext> authorizationManager = mock();
+		AuthorizationManagerFactory<RequestAuthorizationContext> authorizationManagerFactory = mockAuthorizationManagerFactory(
+				authorizationManager);
+		AuthorizationManager<RequestAuthorizationContext> hasAnyAuthority = spy(
+				AuthorityAuthorizationManager.hasAnyAuthority("resource.read", "read"));
+		given(authorizationManagerFactory.hasAnyAuthority(any(String[].class))).willReturn(hasAnyAuthority);
+		AuthorizationManagerFactoryConfig.authorizationManagerFactory = authorizationManagerFactory;
+
+		this.spring.register(AuthorizationManagerFactoryConfig.class, AccessTestController.class).autowire();
+		MockHttpServletRequestBuilder request = get("/resource")
+			.with(user("user").authorities(new SimpleGrantedAuthority("read")));
+		this.mvc.perform(request).andExpect(status().isOk());
+		verify(hasAnyAuthority).authorize(any(), any(RequestAuthorizationContext.class));
+		verifyNoMoreInteractions(hasAnyAuthority);
+		verifyNoInteractions(authorizationManager);
+	}
+
+	@Test
+	public void getWhenCustomAuthorizationManagerFactoryRegisteredAndAuthenticatedThenRespondsWithOk()
+			throws Exception {
+		AuthorizationManager<RequestAuthorizationContext> authorizationManager = mock();
+		AuthorizationManagerFactory<RequestAuthorizationContext> authorizationManagerFactory = mockAuthorizationManagerFactory(
+				authorizationManager);
+		AuthorizationManager<RequestAuthorizationContext> authenticated = spy(
+				AuthenticatedAuthorizationManager.authenticated());
+		given(authorizationManagerFactory.authenticated()).willReturn(authenticated);
+		AuthorizationManagerFactoryConfig.authorizationManagerFactory = authorizationManagerFactory;
+
+		this.spring.register(AuthorizationManagerFactoryConfig.class, AccessTestController.class).autowire();
+		MockHttpServletRequestBuilder request = get("/authenticated").with(user("user"));
+		this.mvc.perform(request).andExpect(status().isOk());
+		verify(authenticated).authorize(any(), any(RequestAuthorizationContext.class));
+		verifyNoMoreInteractions(authenticated);
+		verifyNoInteractions(authorizationManager);
+	}
+
+	@Test
+	public void getWhenCustomAuthorizationManagerFactoryRegisteredAndFullyAuthenticatedThenRespondsWithOk()
+			throws Exception {
+		AuthorizationManager<RequestAuthorizationContext> authorizationManager = mock();
+		AuthorizationManagerFactory<RequestAuthorizationContext> authorizationManagerFactory = mockAuthorizationManagerFactory(
+				authorizationManager);
+		AuthorizationManager<RequestAuthorizationContext> fullyAuthenticated = spy(
+				AuthenticatedAuthorizationManager.fullyAuthenticated());
+		given(authorizationManagerFactory.fullyAuthenticated()).willReturn(fullyAuthenticated);
+		AuthorizationManagerFactoryConfig.authorizationManagerFactory = authorizationManagerFactory;
+
+		this.spring.register(AuthorizationManagerFactoryConfig.class, AccessTestController.class).autowire();
+		MockHttpServletRequestBuilder request = get("/fully-authenticated").with(user("user"));
+		this.mvc.perform(request).andExpect(status().isOk());
+		verify(fullyAuthenticated).authorize(any(), any(RequestAuthorizationContext.class));
+		verifyNoMoreInteractions(fullyAuthenticated);
+		verifyNoInteractions(authorizationManager);
+	}
+
+	@Test
+	public void getWhenCustomAuthorizationManagerFactoryRegisteredAndRememberMeThenRespondsWithOk() throws Exception {
+		AuthorizationManager<RequestAuthorizationContext> authorizationManager = mock();
+		AuthorizationManagerFactory<RequestAuthorizationContext> authorizationManagerFactory = mockAuthorizationManagerFactory(
+				authorizationManager);
+		AuthorizationManager<RequestAuthorizationContext> rememberMe = spy(
+				AuthenticatedAuthorizationManager.rememberMe());
+		given(authorizationManagerFactory.rememberMe()).willReturn(rememberMe);
+		AuthorizationManagerFactoryConfig.authorizationManagerFactory = authorizationManagerFactory;
+
+		this.spring.register(AuthorizationManagerFactoryConfig.class, AccessTestController.class).autowire();
+		MockHttpServletRequestBuilder request = get("/remember-me")
+			.with(authentication(new RememberMeAuthenticationToken("test", "user", Set.of())));
+		this.mvc.perform(request).andExpect(status().isOk());
+		verify(rememberMe).authorize(any(), any(RequestAuthorizationContext.class));
+		verifyNoMoreInteractions(rememberMe);
+		verifyNoInteractions(authorizationManager);
+	}
+
+	@Test
+	public void getWhenCustomAuthorizationManagerFactoryRegisteredAndAnonymousThenRespondsWithOk() throws Exception {
+		AuthorizationManager<RequestAuthorizationContext> authorizationManager = mock();
+		AuthorizationManagerFactory<RequestAuthorizationContext> authorizationManagerFactory = mockAuthorizationManagerFactory(
+				authorizationManager);
+		AuthorizationManager<RequestAuthorizationContext> anonymous = spy(
+				AuthenticatedAuthorizationManager.anonymous());
+		given(authorizationManagerFactory.anonymous()).willReturn(anonymous);
+		AuthorizationManagerFactoryConfig.authorizationManagerFactory = authorizationManagerFactory;
+
+		this.spring.register(AuthorizationManagerFactoryConfig.class, AccessTestController.class).autowire();
+		MockHttpServletRequestBuilder request = get("/anonymous").with(anonymous());
+		this.mvc.perform(request).andExpect(status().isOk());
+		verify(anonymous).authorize(any(), any(RequestAuthorizationContext.class));
+		verifyNoMoreInteractions(anonymous);
+		verifyNoInteractions(authorizationManager);
+	}
+
+	@Test
+	public void getWhenCustomAuthorizationManagerFactoryRegisteredAndAccessThenRespondsWithForbidden()
+			throws Exception {
+		AuthorizationManager<RequestAuthorizationContext> authorizationManager = mock();
+		AuthorizationManagerFactoryConfig.authorizationManagerFactory = mockAuthorizationManagerFactory(
+				authorizationManager);
+
+		this.spring.register(AuthorizationManagerFactoryConfig.class).autowire();
+		MockHttpServletRequestBuilder request = get("/").with(user("user"));
+		this.mvc.perform(request).andExpect(status().isForbidden());
+		verifyNoInteractions(authorizationManager);
+	}
+
 	@Test
 	@Test
 	public void getWhenExpressionHasIpAddressLocalhostConfiguredIpAddressIsLocalhostThenRespondsWithOk()
 	public void getWhenExpressionHasIpAddressLocalhostConfiguredIpAddressIsLocalhostThenRespondsWithOk()
 			throws Exception {
 			throws Exception {
@@ -587,6 +816,23 @@ public class AuthorizeHttpRequestsConfigurerTests {
 		};
 		};
 	}
 	}
 
 
+	private AuthorizationManagerFactory<RequestAuthorizationContext> mockAuthorizationManagerFactory(
+			AuthorizationManager<RequestAuthorizationContext> authorizationManager) {
+		AuthorizationManagerFactory<RequestAuthorizationContext> authorizationManagerFactory = mock();
+		given(authorizationManagerFactory.permitAll()).willReturn(authorizationManager);
+		given(authorizationManagerFactory.denyAll()).willReturn(authorizationManager);
+		given(authorizationManagerFactory.hasRole(anyString())).willReturn(authorizationManager);
+		given(authorizationManagerFactory.hasAnyRole(any(String[].class))).willReturn(authorizationManager);
+		given(authorizationManagerFactory.hasAuthority(anyString())).willReturn(authorizationManager);
+		given(authorizationManagerFactory.hasAnyAuthority(any(String[].class))).willReturn(authorizationManager);
+		given(authorizationManagerFactory.authenticated()).willReturn(authorizationManager);
+		given(authorizationManagerFactory.fullyAuthenticated()).willReturn(authorizationManager);
+		given(authorizationManagerFactory.rememberMe()).willReturn(authorizationManager);
+		given(authorizationManagerFactory.anonymous()).willReturn(authorizationManager);
+
+		return authorizationManagerFactory;
+	}
+
 	@Test
 	@Test
 	public void getWhenFullyAuthenticatedConfiguredAndRememberMeTokenThenRespondsWithUnauthorized() throws Exception {
 	public void getWhenFullyAuthenticatedConfiguredAndRememberMeTokenThenRespondsWithUnauthorized() throws Exception {
 		this.spring.register(FullyAuthenticatedConfig.class, BasicController.class).autowire();
 		this.spring.register(FullyAuthenticatedConfig.class, BasicController.class).autowire();
@@ -850,6 +1096,41 @@ public class AuthorizeHttpRequestsConfigurerTests {
 
 
 	}
 	}
 
 
+	@Configuration
+	@EnableWebSecurity
+	static class AuthorizationManagerFactoryConfig {
+
+		static AuthorizationManagerFactory<RequestAuthorizationContext> authorizationManagerFactory;
+
+		@Bean
+		AuthorizationManagerFactory<RequestAuthorizationContext> authorizationManagerFactory() {
+			return authorizationManagerFactory;
+		}
+
+		@Bean
+		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.authorizeHttpRequests((authorize) -> authorize
+					.requestMatchers("/public").permitAll()
+					.requestMatchers("/private").denyAll()
+					.requestMatchers("/admin").hasRole("ADMIN")
+					.requestMatchers("/user").hasAnyRole("USER", "ADMIN")
+					.requestMatchers(HttpMethod.POST, "/resource").hasAuthority("write")
+					.requestMatchers("/resource").hasAnyAuthority("resource.read", "read")
+					.requestMatchers("/authenticated").authenticated()
+					.requestMatchers("/fully-authenticated").fullyAuthenticated()
+					.requestMatchers("/remember-me").rememberMe()
+					.requestMatchers("/anonymous").anonymous()
+					.anyRequest().access((authentication, context) -> new AuthorizationDecision(false))
+				);
+			// @formatter:on
+
+			return http.build();
+		}
+
+	}
+
 	@Configuration
 	@Configuration
 	@EnableWebSecurity
 	@EnableWebSecurity
 	static class ObjectPostProcessorConfig {
 	static class ObjectPostProcessorConfig {
@@ -1295,6 +1576,47 @@ public class AuthorizeHttpRequestsConfigurerTests {
 
 
 	}
 	}
 
 
+	@RestController
+	static class AccessTestController {
+
+		@RequestMapping("/public")
+		void publicEndpoint() {
+		}
+
+		@RequestMapping("/private")
+		void privateEndpoint() {
+		}
+
+		@RequestMapping("/admin")
+		void adminEndpoint() {
+		}
+
+		@RequestMapping("/user")
+		void userEndpoint() {
+		}
+
+		@RequestMapping("/resource")
+		void resourceEndpoint() {
+		}
+
+		@RequestMapping("/authenticated")
+		void authenticatedEndpoint() {
+		}
+
+		@RequestMapping("/fully-authenticated")
+		void fullyAuthenticatedEndpoint() {
+		}
+
+		@RequestMapping("/remember-me")
+		void rememberMeEndpoint() {
+		}
+
+		@RequestMapping("/anonymous")
+		void anonymousEndpoint() {
+		}
+
+	}
+
 	@Configuration
 	@Configuration
 	static class ObservationRegistryConfig {
 	static class ObservationRegistryConfig {
 
 

+ 53 - 1
core/src/main/java/org/springframework/security/access/expression/AbstractSecurityExpressionHandler.java

@@ -28,6 +28,8 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
 import org.springframework.expression.spel.support.StandardEvaluationContext;
 import org.springframework.expression.spel.support.StandardEvaluationContext;
 import org.springframework.security.access.PermissionEvaluator;
 import org.springframework.security.access.PermissionEvaluator;
 import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
 import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
+import org.springframework.security.authorization.AuthorizationManagerFactory;
+import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.Authentication;
 import org.springframework.util.Assert;
 import org.springframework.util.Assert;
 
 
@@ -38,6 +40,7 @@ import org.springframework.util.Assert;
  *
  *
  * @author Luke Taylor
  * @author Luke Taylor
  * @author Evgeniy Cheban
  * @author Evgeniy Cheban
+ * @author Steve Riesenberg
  * @since 3.1
  * @since 3.1
  */
  */
 public abstract class AbstractSecurityExpressionHandler<T>
 public abstract class AbstractSecurityExpressionHandler<T>
@@ -49,6 +52,8 @@ public abstract class AbstractSecurityExpressionHandler<T>
 
 
 	private @Nullable RoleHierarchy roleHierarchy;
 	private @Nullable RoleHierarchy roleHierarchy;
 
 
+	private AuthorizationManagerFactory<T> authorizationManagerFactory = new DefaultAuthorizationManagerFactory<>();
+
 	private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator();
 	private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator();
 
 
 	@Override
 	@Override
@@ -106,11 +111,58 @@ public abstract class AbstractSecurityExpressionHandler<T>
 	protected abstract SecurityExpressionOperations createSecurityExpressionRoot(
 	protected abstract SecurityExpressionOperations createSecurityExpressionRoot(
 			@Nullable Authentication authentication, T invocation);
 			@Nullable Authentication authentication, T invocation);
 
 
+	/**
+	 * Sets the {@link AuthorizationManagerFactory} to be used. The default is
+	 * {@link DefaultAuthorizationManagerFactory}.
+	 * @param authorizationManagerFactory the {@link AuthorizationManagerFactory} to use.
+	 * Cannot be null.
+	 * @since 7.0
+	 */
+	public final void setAuthorizationManagerFactory(AuthorizationManagerFactory<T> authorizationManagerFactory) {
+		Assert.notNull(authorizationManagerFactory, "authorizationManagerFactory cannot be null");
+		this.authorizationManagerFactory = authorizationManagerFactory;
+	}
+
+	protected final AuthorizationManagerFactory<T> getAuthorizationManagerFactory() {
+		return this.authorizationManagerFactory;
+	}
+
+	/**
+	 * Allows accessing the {@link DefaultAuthorizationManagerFactory} for getting and
+	 * setting defaults. This method will be removed in Spring Security 8.
+	 * @return the {@link DefaultAuthorizationManagerFactory}
+	 * @throws IllegalStateException if a different {@link AuthorizationManagerFactory}
+	 * was already set
+	 * @deprecated Use
+	 * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
+	 */
+	@Deprecated(since = "7.0")
+	protected final DefaultAuthorizationManagerFactory<T> getDefaultAuthorizationManagerFactory() {
+		if (!(this.authorizationManagerFactory instanceof DefaultAuthorizationManagerFactory<T> defaultAuthorizationManagerFactory)) {
+			throw new IllegalStateException(
+					"authorizationManagerFactory must be an instance of DefaultAuthorizationManagerFactory");
+		}
+
+		return defaultAuthorizationManagerFactory;
+	}
+
+	/**
+	 * @deprecated Use {@link #getDefaultAuthorizationManagerFactory()} instead
+	 */
+	@Deprecated(since = "7.0")
 	protected @Nullable RoleHierarchy getRoleHierarchy() {
 	protected @Nullable RoleHierarchy getRoleHierarchy() {
 		return this.roleHierarchy;
 		return this.roleHierarchy;
 	}
 	}
 
 
-	public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
+	/**
+	 * @deprecated Use
+	 * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
+	 */
+	@Deprecated(since = "7.0")
+	public void setRoleHierarchy(@Nullable RoleHierarchy roleHierarchy) {
+		if (roleHierarchy != null) {
+			getDefaultAuthorizationManagerFactory().setRoleHierarchy(roleHierarchy);
+		}
 		this.roleHierarchy = roleHierarchy;
 		this.roleHierarchy = roleHierarchy;
 	}
 	}
 
 

+ 113 - 64
core/src/main/java/org/springframework/security/access/expression/SecurityExpressionRoot.java

@@ -17,8 +17,6 @@
 package org.springframework.security.access.expression;
 package org.springframework.security.access.expression;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
-import java.util.Collection;
-import java.util.Set;
 import java.util.function.Supplier;
 import java.util.function.Supplier;
 
 
 import org.jspecify.annotations.Nullable;
 import org.jspecify.annotations.Nullable;
@@ -26,10 +24,11 @@ import org.jspecify.annotations.Nullable;
 import org.springframework.security.access.PermissionEvaluator;
 import org.springframework.security.access.PermissionEvaluator;
 import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
 import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
 import org.springframework.security.authentication.AuthenticationTrustResolver;
 import org.springframework.security.authentication.AuthenticationTrustResolver;
-import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
+import org.springframework.security.authorization.AuthorizationManager;
+import org.springframework.security.authorization.AuthorizationManagerFactory;
+import org.springframework.security.authorization.AuthorizationResult;
+import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.Authentication;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.authority.AuthorityUtils;
 import org.springframework.util.Assert;
 import org.springframework.util.Assert;
 import org.springframework.util.function.SingletonSupplier;
 import org.springframework.util.function.SingletonSupplier;
 
 
@@ -38,19 +37,18 @@ import org.springframework.util.function.SingletonSupplier;
  *
  *
  * @author Luke Taylor
  * @author Luke Taylor
  * @author Evgeniy Cheban
  * @author Evgeniy Cheban
+ * @author Steve Riesenberg
  * @since 3.0
  * @since 3.0
  */
  */
-public abstract class SecurityExpressionRoot implements SecurityExpressionOperations {
+public abstract class SecurityExpressionRoot<T extends @Nullable Object> implements SecurityExpressionOperations {
 
 
 	private final Supplier<Authentication> authentication;
 	private final Supplier<Authentication> authentication;
 
 
-	private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
-
-	private @Nullable RoleHierarchy roleHierarchy;
+	private String defaultRolePrefix = "ROLE_";
 
 
-	private @Nullable Set<String> roles;
+	private final T object;
 
 
-	private String defaultRolePrefix = "ROLE_";
+	private AuthorizationManagerFactory<T> authorizationManagerFactory = new DefaultAuthorizationManagerFactory<>();
 
 
 	/**
 	/**
 	 * Allows "permitAll" expression
 	 * Allows "permitAll" expression
@@ -77,9 +75,12 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
 	/**
 	/**
 	 * Creates a new instance
 	 * Creates a new instance
 	 * @param authentication the {@link Authentication} to use. Cannot be null.
 	 * @param authentication the {@link Authentication} to use. Cannot be null.
+	 * @deprecated Use {@link #SecurityExpressionRoot(Supplier, Object)} instead
 	 */
 	 */
+	@Deprecated(since = "7.0")
+	@SuppressWarnings("NullAway")
 	public SecurityExpressionRoot(@Nullable Authentication authentication) {
 	public SecurityExpressionRoot(@Nullable Authentication authentication) {
-		this(() -> authentication);
+		this(() -> authentication, null);
 	}
 	}
 
 
 	/**
 	/**
@@ -88,44 +89,70 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
 	 * @param authentication the {@link Supplier} of the {@link Authentication} to use.
 	 * @param authentication the {@link Supplier} of the {@link Authentication} to use.
 	 * Cannot be null.
 	 * Cannot be null.
 	 * @since 5.8
 	 * @since 5.8
+	 * @deprecated Use {@link #SecurityExpressionRoot(Supplier, Object)} instead
+	 */
+	@Deprecated(since = "7.0")
+	@SuppressWarnings("NullAway")
+	public SecurityExpressionRoot(Supplier<@Nullable Authentication> authentication) {
+		this(authentication, null);
+	}
+
+	/**
+	 * Creates a new instance that uses lazy initialization of the {@link Authentication}
+	 * object.
+	 * @param authentication the {@link Supplier} of the {@link Authentication} to use.
+	 * Cannot be null.
+	 * @param object the object being authorized
+	 * @since 7.0
 	 */
 	 */
-	public SecurityExpressionRoot(Supplier<? extends @Nullable Authentication> authentication) {
+	public SecurityExpressionRoot(Supplier<? extends @Nullable Authentication> authentication, T object) {
 		this.authentication = SingletonSupplier.of(() -> {
 		this.authentication = SingletonSupplier.of(() -> {
 			Authentication value = authentication.get();
 			Authentication value = authentication.get();
 			Assert.notNull(value, "Authentication object cannot be null");
 			Assert.notNull(value, "Authentication object cannot be null");
 			return value;
 			return value;
 		});
 		});
+		this.object = object;
 	}
 	}
 
 
 	@Override
 	@Override
 	public final boolean hasAuthority(String authority) {
 	public final boolean hasAuthority(String authority) {
-		return hasAnyAuthority(authority);
+		return isGranted(this.authorizationManagerFactory.hasAnyAuthority(authority));
 	}
 	}
 
 
 	@Override
 	@Override
 	public final boolean hasAnyAuthority(String... authorities) {
 	public final boolean hasAnyAuthority(String... authorities) {
-		return hasAnyAuthorityName(null, authorities);
+		return isGranted(this.authorizationManagerFactory.hasAnyAuthority(authorities));
 	}
 	}
 
 
 	@Override
 	@Override
 	public final boolean hasRole(String role) {
 	public final boolean hasRole(String role) {
-		return hasAnyRole(role);
+		if (this.authorizationManagerFactory instanceof DefaultAuthorizationManagerFactory<T>) {
+			// To provide passivity for old behavior where hasRole('ROLE_A') is allowed,
+			// we strip the role prefix when found.
+			// TODO: Remove in favor of fixing inconsistent behavior?
+			String rolePrefix = this.defaultRolePrefix;
+			if (role.startsWith(rolePrefix)) {
+				role = role.substring(rolePrefix.length());
+			}
+		}
+		return isGranted(this.authorizationManagerFactory.hasRole(role));
 	}
 	}
 
 
 	@Override
 	@Override
 	public final boolean hasAnyRole(String... roles) {
 	public final boolean hasAnyRole(String... roles) {
-		return hasAnyAuthorityName(this.defaultRolePrefix, roles);
-	}
-
-	private boolean hasAnyAuthorityName(@Nullable String prefix, String... roles) {
-		Set<String> roleSet = getAuthoritySet();
-		for (String role : roles) {
-			String defaultedRole = getRoleWithDefaultPrefix(prefix, role);
-			if (roleSet.contains(defaultedRole)) {
-				return true;
+		if (this.authorizationManagerFactory instanceof DefaultAuthorizationManagerFactory<T>) {
+			// To provide passivity for old behavior where hasRole('ROLE_A') is allowed,
+			// we strip the role prefix when found.
+			// TODO: Remove in favor of fixing inconsistent behavior?
+			String rolePrefix = this.defaultRolePrefix;
+			for (int index = 0; index < roles.length; index++) {
+				String role = roles[index];
+				if (role.startsWith(rolePrefix)) {
+					roles[index] = role.substring(rolePrefix.length());
+				}
 			}
 			}
 		}
 		}
-		return false;
+		return isGranted(this.authorizationManagerFactory.hasAnyRole(roles));
 	}
 	}
 
 
 	@Override
 	@Override
@@ -135,33 +162,37 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
 
 
 	@Override
 	@Override
 	public final boolean permitAll() {
 	public final boolean permitAll() {
-		return true;
+		return isGranted(this.authorizationManagerFactory.permitAll());
 	}
 	}
 
 
 	@Override
 	@Override
 	public final boolean denyAll() {
 	public final boolean denyAll() {
-		return false;
+		return isGranted(this.authorizationManagerFactory.denyAll());
 	}
 	}
 
 
 	@Override
 	@Override
 	public final boolean isAnonymous() {
 	public final boolean isAnonymous() {
-		return this.trustResolver.isAnonymous(getAuthentication());
+		return isGranted(this.authorizationManagerFactory.anonymous());
 	}
 	}
 
 
 	@Override
 	@Override
 	public final boolean isAuthenticated() {
 	public final boolean isAuthenticated() {
-		return this.trustResolver.isAuthenticated(getAuthentication());
+		return isGranted(this.authorizationManagerFactory.authenticated());
 	}
 	}
 
 
 	@Override
 	@Override
 	public final boolean isRememberMe() {
 	public final boolean isRememberMe() {
-		return this.trustResolver.isRememberMe(getAuthentication());
+		return isGranted(this.authorizationManagerFactory.rememberMe());
 	}
 	}
 
 
 	@Override
 	@Override
 	public final boolean isFullyAuthenticated() {
 	public final boolean isFullyAuthenticated() {
-		Authentication authentication = getAuthentication();
-		return this.trustResolver.isFullyAuthenticated(authentication);
+		return isGranted(this.authorizationManagerFactory.fullyAuthenticated());
+	}
+
+	private boolean isGranted(AuthorizationManager<T> authorizationManager) {
+		AuthorizationResult authorizationResult = authorizationManager.authorize(this.authentication, this.object);
+		return (authorizationResult != null && authorizationResult.isGranted());
 	}
 	}
 
 
 	/**
 	/**
@@ -173,12 +204,24 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
 		return getAuthentication().getPrincipal();
 		return getAuthentication().getPrincipal();
 	}
 	}
 
 
+	/**
+	 * @deprecated Use
+	 * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
+	 */
+	@Deprecated(since = "7.0")
 	public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
 	public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
-		this.trustResolver = trustResolver;
+		getDefaultAuthorizationManagerFactory().setTrustResolver(trustResolver);
 	}
 	}
 
 
+	/**
+	 * @deprecated Use
+	 * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
+	 */
+	@Deprecated(since = "7.0")
 	public void setRoleHierarchy(@Nullable RoleHierarchy roleHierarchy) {
 	public void setRoleHierarchy(@Nullable RoleHierarchy roleHierarchy) {
-		this.roleHierarchy = roleHierarchy;
+		if (roleHierarchy != null) {
+			getDefaultAuthorizationManagerFactory().setRoleHierarchy(roleHierarchy);
+		}
 	}
 	}
 
 
 	/**
 	/**
@@ -193,20 +236,46 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
 	 * If null or empty, then no default role prefix is used.
 	 * If null or empty, then no default role prefix is used.
 	 * </p>
 	 * </p>
 	 * @param defaultRolePrefix the default prefix to add to roles. Default "ROLE_".
 	 * @param defaultRolePrefix the default prefix to add to roles. Default "ROLE_".
+	 * @deprecated Use
+	 * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
 	 */
 	 */
-	public void setDefaultRolePrefix(String defaultRolePrefix) {
+	@Deprecated(since = "7.0")
+	public void setDefaultRolePrefix(@Nullable String defaultRolePrefix) {
+		if (defaultRolePrefix == null) {
+			defaultRolePrefix = "";
+		}
+		getDefaultAuthorizationManagerFactory().setRolePrefix(defaultRolePrefix);
 		this.defaultRolePrefix = defaultRolePrefix;
 		this.defaultRolePrefix = defaultRolePrefix;
 	}
 	}
 
 
-	private Set<String> getAuthoritySet() {
-		if (this.roles == null) {
-			Collection<? extends GrantedAuthority> userAuthorities = getAuthentication().getAuthorities();
-			if (this.roleHierarchy != null) {
-				userAuthorities = this.roleHierarchy.getReachableGrantedAuthorities(userAuthorities);
-			}
-			this.roles = AuthorityUtils.authorityListToSet(userAuthorities);
+	/**
+	 * Sets the {@link AuthorizationManagerFactory} to use for creating instances of
+	 * {@link AuthorizationManager}.
+	 * @param authorizationManagerFactory the {@link AuthorizationManagerFactory} to use
+	 * @since 7.0
+	 */
+	public void setAuthorizationManagerFactory(AuthorizationManagerFactory<T> authorizationManagerFactory) {
+		Assert.notNull(authorizationManagerFactory, "authorizationManagerFactory cannot be null");
+		this.authorizationManagerFactory = authorizationManagerFactory;
+	}
+
+	/**
+	 * Allows accessing the {@link DefaultAuthorizationManagerFactory} for getting and
+	 * setting defaults. This method will be removed in Spring Security 8.
+	 * @return the {@link DefaultAuthorizationManagerFactory}
+	 * @throws IllegalStateException if a different {@link AuthorizationManagerFactory}
+	 * was already set
+	 * @deprecated Use
+	 * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
+	 */
+	@Deprecated(since = "7.0", forRemoval = true)
+	private DefaultAuthorizationManagerFactory<T> getDefaultAuthorizationManagerFactory() {
+		if (!(this.authorizationManagerFactory instanceof DefaultAuthorizationManagerFactory<T> defaultAuthorizationManagerFactory)) {
+			throw new IllegalStateException(
+					"authorizationManagerFactory must be an instance of DefaultAuthorizationManagerFactory");
 		}
 		}
-		return this.roles;
+
+		return defaultAuthorizationManagerFactory;
 	}
 	}
 
 
 	@Override
 	@Override
@@ -225,24 +294,4 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
 		this.permissionEvaluator = permissionEvaluator;
 		this.permissionEvaluator = permissionEvaluator;
 	}
 	}
 
 
-	/**
-	 * Prefixes role with defaultRolePrefix if defaultRolePrefix is non-null and if role
-	 * does not already start with defaultRolePrefix.
-	 * @param defaultRolePrefix
-	 * @param role
-	 * @return
-	 */
-	private static String getRoleWithDefaultPrefix(@Nullable String defaultRolePrefix, String role) {
-		if (role == null) {
-			return role;
-		}
-		if (defaultRolePrefix == null || defaultRolePrefix.length() == 0) {
-			return role;
-		}
-		if (role.startsWith(defaultRolePrefix)) {
-			return role;
-		}
-		return defaultRolePrefix + role;
-	}
-
 }
 }

+ 29 - 6
core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java

@@ -43,6 +43,7 @@ import org.springframework.security.access.expression.AbstractSecurityExpression
 import org.springframework.security.access.expression.ExpressionUtils;
 import org.springframework.security.access.expression.ExpressionUtils;
 import org.springframework.security.authentication.AuthenticationTrustResolver;
 import org.springframework.security.authentication.AuthenticationTrustResolver;
 import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
 import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
+import org.springframework.security.authorization.AuthorizationManagerFactory;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.parameters.DefaultSecurityParameterNameDiscoverer;
 import org.springframework.security.core.parameters.DefaultSecurityParameterNameDiscoverer;
 import org.springframework.util.Assert;
 import org.springframework.util.Assert;
@@ -56,11 +57,14 @@ import org.springframework.util.Assert;
  * @author Luke Taylor
  * @author Luke Taylor
  * @author Evgeniy Cheban
  * @author Evgeniy Cheban
  * @author Blagoja Stamatovski
  * @author Blagoja Stamatovski
+ * @author Steve Riesenberg
  * @since 3.0
  * @since 3.0
  */
  */
 public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpressionHandler<MethodInvocation>
 public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpressionHandler<MethodInvocation>
 		implements MethodSecurityExpressionHandler {
 		implements MethodSecurityExpressionHandler {
 
 
+	private static final String DEFAULT_ROLE_PREFIX = "ROLE_";
+
 	protected final Log logger = LogFactory.getLog(getClass());
 	protected final Log logger = LogFactory.getLog(getClass());
 
 
 	private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
 	private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
@@ -69,7 +73,7 @@ public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpr
 
 
 	private @Nullable PermissionCacheOptimizer permissionCacheOptimizer = null;
 	private @Nullable PermissionCacheOptimizer permissionCacheOptimizer = null;
 
 
-	private String defaultRolePrefix = "ROLE_";
+	private String defaultRolePrefix = DEFAULT_ROLE_PREFIX;
 
 
 	public DefaultMethodSecurityExpressionHandler() {
 	public DefaultMethodSecurityExpressionHandler() {
 	}
 	}
@@ -106,12 +110,14 @@ public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpr
 
 
 	private MethodSecurityExpressionOperations createSecurityExpressionRoot(
 	private MethodSecurityExpressionOperations createSecurityExpressionRoot(
 			Supplier<? extends @Nullable Authentication> authentication, MethodInvocation invocation) {
 			Supplier<? extends @Nullable Authentication> authentication, MethodInvocation invocation) {
-		MethodSecurityExpressionRoot root = new MethodSecurityExpressionRoot(authentication);
+		MethodSecurityExpressionRoot root = new MethodSecurityExpressionRoot(authentication, invocation);
 		root.setThis(invocation.getThis());
 		root.setThis(invocation.getThis());
+		root.setAuthorizationManagerFactory(getAuthorizationManagerFactory());
 		root.setPermissionEvaluator(getPermissionEvaluator());
 		root.setPermissionEvaluator(getPermissionEvaluator());
-		root.setTrustResolver(getTrustResolver());
-		Optional.ofNullable(getRoleHierarchy()).ifPresent(root::setRoleHierarchy);
-		root.setDefaultRolePrefix(getDefaultRolePrefix());
+		if (!DEFAULT_ROLE_PREFIX.equals(this.defaultRolePrefix)) {
+			// Ensure SecurityExpressionRoot can strip the custom role prefix
+			root.setDefaultRolePrefix(getDefaultRolePrefix());
+		}
 		return root;
 		return root;
 	}
 	}
 
 
@@ -232,15 +238,22 @@ public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpr
 	 * {@link AuthenticationTrustResolverImpl}.
 	 * {@link AuthenticationTrustResolverImpl}.
 	 * @param trustResolver the {@link AuthenticationTrustResolver} to use. Cannot be
 	 * @param trustResolver the {@link AuthenticationTrustResolver} to use. Cannot be
 	 * null.
 	 * null.
+	 * @deprecated Use
+	 * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
 	 */
 	 */
+	@Deprecated(since = "7.0")
 	public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
 	public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
 		Assert.notNull(trustResolver, "trustResolver cannot be null");
 		Assert.notNull(trustResolver, "trustResolver cannot be null");
+		getDefaultAuthorizationManagerFactory().setTrustResolver(trustResolver);
 		this.trustResolver = trustResolver;
 		this.trustResolver = trustResolver;
 	}
 	}
 
 
 	/**
 	/**
 	 * @return The current {@link AuthenticationTrustResolver}
 	 * @return The current {@link AuthenticationTrustResolver}
+	 * @deprecated Use
+	 * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
 	 */
 	 */
+	@Deprecated(since = "7.0")
 	protected AuthenticationTrustResolver getTrustResolver() {
 	protected AuthenticationTrustResolver getTrustResolver() {
 		return this.trustResolver;
 		return this.trustResolver;
 	}
 	}
@@ -289,14 +302,24 @@ public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpr
 	 * If null or empty, then no default role prefix is used.
 	 * If null or empty, then no default role prefix is used.
 	 * </p>
 	 * </p>
 	 * @param defaultRolePrefix the default prefix to add to roles. Default "ROLE_".
 	 * @param defaultRolePrefix the default prefix to add to roles. Default "ROLE_".
+	 * @deprecated Use
+	 * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
 	 */
 	 */
-	public void setDefaultRolePrefix(String defaultRolePrefix) {
+	@Deprecated(since = "7.0")
+	public void setDefaultRolePrefix(@Nullable String defaultRolePrefix) {
+		if (defaultRolePrefix == null) {
+			defaultRolePrefix = "";
+		}
+		getDefaultAuthorizationManagerFactory().setRolePrefix(defaultRolePrefix);
 		this.defaultRolePrefix = defaultRolePrefix;
 		this.defaultRolePrefix = defaultRolePrefix;
 	}
 	}
 
 
 	/**
 	/**
 	 * @return The default role prefix
 	 * @return The default role prefix
+	 * @deprecated Use
+	 * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
 	 */
 	 */
+	@Deprecated(since = "7.0")
 	protected String getDefaultRolePrefix() {
 	protected String getDefaultRolePrefix() {
 		return this.defaultRolePrefix;
 		return this.defaultRolePrefix;
 	}
 	}

+ 7 - 7
core/src/main/java/org/springframework/security/access/expression/method/MethodSecurityExpressionRoot.java

@@ -18,6 +18,7 @@ package org.springframework.security.access.expression.method;
 
 
 import java.util.function.Supplier;
 import java.util.function.Supplier;
 
 
+import org.aopalliance.intercept.MethodInvocation;
 import org.jspecify.annotations.Nullable;
 import org.jspecify.annotations.Nullable;
 
 
 import org.springframework.security.access.expression.SecurityExpressionRoot;
 import org.springframework.security.access.expression.SecurityExpressionRoot;
@@ -28,9 +29,11 @@ import org.springframework.security.core.Authentication;
  *
  *
  * @author Luke Taylor
  * @author Luke Taylor
  * @author Evgeniy Cheban
  * @author Evgeniy Cheban
+ * @author Steve Riesenberg
  * @since 3.0
  * @since 3.0
  */
  */
-class MethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
+class MethodSecurityExpressionRoot extends SecurityExpressionRoot<MethodInvocation>
+		implements MethodSecurityExpressionOperations {
 
 
 	private @Nullable Object filterObject;
 	private @Nullable Object filterObject;
 
 
@@ -38,12 +41,9 @@ class MethodSecurityExpressionRoot extends SecurityExpressionRoot implements Met
 
 
 	private @Nullable Object target;
 	private @Nullable Object target;
 
 
-	MethodSecurityExpressionRoot(@Nullable Authentication a) {
-		super(a);
-	}
-
-	MethodSecurityExpressionRoot(Supplier<? extends @Nullable Authentication> authentication) {
-		super(authentication);
+	MethodSecurityExpressionRoot(Supplier<? extends @Nullable Authentication> authentication,
+			MethodInvocation methodInvocation) {
+		super(authentication, methodInvocation);
 	}
 	}
 
 
 	@Override
 	@Override

+ 124 - 0
core/src/main/java/org/springframework/security/authorization/AuthorizationManagerFactory.java

@@ -0,0 +1,124 @@
+/*
+ * 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 org.jspecify.annotations.Nullable;
+
+/**
+ * A factory for creating different kinds of {@link AuthorizationManager} instances.
+ *
+ * @param <T> the type of object that the authorization check is being done on
+ * @author Steve Riesenberg
+ * @since 7.0
+ */
+public interface AuthorizationManagerFactory<T extends @Nullable Object> {
+
+	/**
+	 * Create an {@link AuthorizationManager} that allows anyone.
+	 * @return A new {@link AuthorizationManager} instance
+	 */
+	default AuthorizationManager<T> permitAll() {
+		return SingleResultAuthorizationManager.permitAll();
+	}
+
+	/**
+	 * Creates an {@link AuthorizationManager} that does not allow anyone.
+	 * @return A new {@link AuthorizationManager} instance
+	 */
+	default AuthorizationManager<T> denyAll() {
+		return SingleResultAuthorizationManager.denyAll();
+	}
+
+	/**
+	 * Creates an {@link AuthorizationManager} that requires users to have the specified
+	 * role.
+	 * @param role the role (automatically prepended with ROLE_) that should be required
+	 * to allow access (i.e. USER, ADMIN, etc.)
+	 * @return A new {@link AuthorizationManager} instance
+	 */
+	default AuthorizationManager<T> hasRole(String role) {
+		return AuthorityAuthorizationManager.hasRole(role);
+	}
+
+	/**
+	 * Creates an {@link AuthorizationManager} that requires users to have one of many
+	 * roles.
+	 * @param roles the roles (automatically prepended with ROLE_) that the user should
+	 * have at least one of to allow access (i.e. USER, ADMIN, etc.)
+	 * @return A new {@link AuthorizationManager} instance
+	 */
+	default AuthorizationManager<T> hasAnyRole(String... roles) {
+		return AuthorityAuthorizationManager.hasAnyRole(roles);
+	}
+
+	/**
+	 * Creates an {@link AuthorizationManager} that requires users to have the specified
+	 * authority.
+	 * @param authority the authority that should be required to allow access (i.e.
+	 * ROLE_USER, ROLE_ADMIN, etc.)
+	 * @return A new {@link AuthorizationManager} instance
+	 */
+	default AuthorizationManager<T> hasAuthority(String authority) {
+		return AuthorityAuthorizationManager.hasAuthority(authority);
+	}
+
+	/**
+	 * Creates an {@link AuthorizationManager} that requires users to have one of many
+	 * authorities.
+	 * @param authorities the authorities that the user should have at least one of to
+	 * allow access (i.e. ROLE_USER, ROLE_ADMIN, etc.)
+	 * @return A new {@link AuthorizationManager} instance
+	 */
+	default AuthorizationManager<T> hasAnyAuthority(String... authorities) {
+		return AuthorityAuthorizationManager.hasAnyAuthority(authorities);
+	}
+
+	/**
+	 * Creates an {@link AuthorizationManager} that allows any authenticated user.
+	 * @return A new {@link AuthorizationManager} instance
+	 */
+	default AuthorizationManager<T> authenticated() {
+		return AuthenticatedAuthorizationManager.authenticated();
+	}
+
+	/**
+	 * Creates an {@link AuthorizationManager} that allows users who have authenticated
+	 * and were not remembered.
+	 * @return A new {@link AuthorizationManager} instance
+	 */
+	default AuthorizationManager<T> fullyAuthenticated() {
+		return AuthenticatedAuthorizationManager.fullyAuthenticated();
+	}
+
+	/**
+	 * Creates an {@link AuthorizationManager} that allows users that have been
+	 * remembered.
+	 * @return A new {@link AuthorizationManager} instance
+	 */
+	default AuthorizationManager<T> rememberMe() {
+		return AuthenticatedAuthorizationManager.rememberMe();
+	}
+
+	/**
+	 * Creates an {@link AuthorizationManager} that allows only anonymous users.
+	 * @return A new {@link AuthorizationManager} instance
+	 */
+	default AuthorizationManager<T> anonymous() {
+		return AuthenticatedAuthorizationManager.anonymous();
+	}
+
+}

+ 123 - 0
core/src/main/java/org/springframework/security/authorization/DefaultAuthorizationManagerFactory.java

@@ -0,0 +1,123 @@
+/*
+ * 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 org.jspecify.annotations.Nullable;
+
+import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
+import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
+import org.springframework.security.authentication.AuthenticationTrustResolver;
+import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
+import org.springframework.util.Assert;
+
+/**
+ * A factory for creating different kinds of {@link AuthorizationManager} instances.
+ *
+ * @param <T> the type of object that the authorization check is being done on
+ * @author Steve Riesenberg
+ * @since 7.0
+ */
+public final class DefaultAuthorizationManagerFactory<T extends @Nullable Object>
+		implements AuthorizationManagerFactory<T> {
+
+	private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
+
+	private RoleHierarchy roleHierarchy = new NullRoleHierarchy();
+
+	private String rolePrefix = "ROLE_";
+
+	/**
+	 * Sets the {@link AuthenticationTrustResolver} used to check the user's
+	 * authentication.
+	 * @param trustResolver the {@link AuthenticationTrustResolver} to use
+	 */
+	public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
+		Assert.notNull(trustResolver, "trustResolver cannot be null");
+		this.trustResolver = trustResolver;
+	}
+
+	/**
+	 * Sets the {@link RoleHierarchy} used to discover reachable authorities.
+	 * @param roleHierarchy the {@link RoleHierarchy} to use
+	 */
+	public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
+		Assert.notNull(roleHierarchy, "roleHierarchy cannot be null");
+		this.roleHierarchy = roleHierarchy;
+	}
+
+	/**
+	 * Sets the prefix used to create an authority name from a role name. Can be an empty
+	 * string.
+	 * @param rolePrefix the role prefix to use
+	 */
+	public void setRolePrefix(String rolePrefix) {
+		Assert.notNull(rolePrefix, "rolePrefix cannot be null");
+		this.rolePrefix = rolePrefix;
+	}
+
+	@Override
+	public AuthorizationManager<T> hasRole(String role) {
+		return hasAnyRole(role);
+	}
+
+	@Override
+	public AuthorizationManager<T> hasAnyRole(String... roles) {
+		return withRoleHierarchy(AuthorityAuthorizationManager.hasAnyRole(this.rolePrefix, roles));
+	}
+
+	@Override
+	public AuthorizationManager<T> hasAuthority(String authority) {
+		return withRoleHierarchy(AuthorityAuthorizationManager.hasAuthority(authority));
+	}
+
+	@Override
+	public AuthorizationManager<T> hasAnyAuthority(String... authorities) {
+		return withRoleHierarchy(AuthorityAuthorizationManager.hasAnyAuthority(authorities));
+	}
+
+	@Override
+	public AuthorizationManager<T> authenticated() {
+		return withTrustResolver(AuthenticatedAuthorizationManager.authenticated());
+	}
+
+	@Override
+	public AuthorizationManager<T> fullyAuthenticated() {
+		return withTrustResolver(AuthenticatedAuthorizationManager.fullyAuthenticated());
+	}
+
+	@Override
+	public AuthorizationManager<T> rememberMe() {
+		return withTrustResolver(AuthenticatedAuthorizationManager.rememberMe());
+	}
+
+	@Override
+	public AuthorizationManager<T> anonymous() {
+		return withTrustResolver(AuthenticatedAuthorizationManager.anonymous());
+	}
+
+	private AuthorityAuthorizationManager<T> withRoleHierarchy(AuthorityAuthorizationManager<T> authorizationManager) {
+		authorizationManager.setRoleHierarchy(this.roleHierarchy);
+		return authorizationManager;
+	}
+
+	private AuthenticatedAuthorizationManager<T> withTrustResolver(
+			AuthenticatedAuthorizationManager<T> authorizationManager) {
+		authorizationManager.setTrustResolver(this.trustResolver);
+		return authorizationManager;
+	}
+
+}

+ 2 - 1
core/src/test/java/org/springframework/security/access/expression/method/MethodSecurityExpressionRootTests.java

@@ -16,6 +16,7 @@
 
 
 package org.springframework.security.access.expression.method;
 package org.springframework.security.access.expression.method;
 
 
+import org.aopalliance.intercept.MethodInvocation;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Test;
 
 
@@ -53,7 +54,7 @@ public class MethodSecurityExpressionRootTests {
 	@BeforeEach
 	@BeforeEach
 	public void createContext() {
 	public void createContext() {
 		this.user = mock(Authentication.class);
 		this.user = mock(Authentication.class);
-		this.root = new MethodSecurityExpressionRoot(this.user);
+		this.root = new MethodSecurityExpressionRoot(() -> this.user, mock(MethodInvocation.class));
 		this.ctx = new StandardEvaluationContext();
 		this.ctx = new StandardEvaluationContext();
 		this.ctx.setRootObject(this.root);
 		this.ctx.setRootObject(this.root);
 		this.trustResolver = mock(AuthenticationTrustResolver.class);
 		this.trustResolver = mock(AuthenticationTrustResolver.class);

+ 100 - 0
core/src/test/java/org/springframework/security/authorization/AuthorizationManagerFactoryTests.java

@@ -0,0 +1,100 @@
+/*
+ * 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 org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link AuthorizationManagerFactory}.
+ *
+ * @author Steve Riesenberg
+ */
+public class AuthorizationManagerFactoryTests {
+
+	@Test
+	public void permitAllReturnsSingleResultAuthorizationManagerByDefault() {
+		AuthorizationManagerFactory<String> factory = new DefaultAuthorizationManagerFactory<>();
+		AuthorizationManager<String> authorizationManager = factory.permitAll();
+		assertThat(authorizationManager).isInstanceOf(SingleResultAuthorizationManager.class);
+	}
+
+	@Test
+	public void denyAllReturnsSingleResultAuthorizationManagerByDefault() {
+		AuthorizationManagerFactory<String> factory = new DefaultAuthorizationManagerFactory<>();
+		AuthorizationManager<String> authorizationManager = factory.denyAll();
+		assertThat(authorizationManager).isInstanceOf(SingleResultAuthorizationManager.class);
+	}
+
+	@Test
+	public void hasRoleReturnsAuthorityAuthorizationManagerByDefault() {
+		AuthorizationManagerFactory<String> factory = new DefaultAuthorizationManagerFactory<>();
+		AuthorizationManager<String> authorizationManager = factory.hasRole("USER");
+		assertThat(authorizationManager).isInstanceOf(AuthorityAuthorizationManager.class);
+	}
+
+	@Test
+	public void hasAnyRoleReturnsAuthorityAuthorizationManagerByDefault() {
+		AuthorizationManagerFactory<String> factory = new DefaultAuthorizationManagerFactory<>();
+		AuthorizationManager<String> authorizationManager = factory.hasAnyRole("USER", "ADMIN");
+		assertThat(authorizationManager).isInstanceOf(AuthorityAuthorizationManager.class);
+	}
+
+	@Test
+	public void hasAuthorityReturnsAuthorityAuthorizationManagerByDefault() {
+		AuthorizationManagerFactory<String> factory = new DefaultAuthorizationManagerFactory<>();
+		AuthorizationManager<String> authorizationManager = factory.hasAuthority("authority1");
+		assertThat(authorizationManager).isInstanceOf(AuthorityAuthorizationManager.class);
+	}
+
+	@Test
+	public void hasAnyAuthorityReturnsAuthorityAuthorizationManagerByDefault() {
+		AuthorizationManagerFactory<String> factory = new DefaultAuthorizationManagerFactory<>();
+		AuthorizationManager<String> authorizationManager = factory.hasAnyAuthority("authority1", "authority2");
+		assertThat(authorizationManager).isInstanceOf(AuthorityAuthorizationManager.class);
+	}
+
+	@Test
+	public void authenticatedReturnsAuthenticatedAuthorizationManagerByDefault() {
+		AuthorizationManagerFactory<String> factory = new DefaultAuthorizationManagerFactory<>();
+		AuthorizationManager<String> authorizationManager = factory.authenticated();
+		assertThat(authorizationManager).isInstanceOf(AuthenticatedAuthorizationManager.class);
+	}
+
+	@Test
+	public void fullyAuthenticatedReturnsAuthenticatedAuthorizationManagerByDefault() {
+		AuthorizationManagerFactory<String> factory = new DefaultAuthorizationManagerFactory<>();
+		AuthorizationManager<String> authorizationManager = factory.fullyAuthenticated();
+		assertThat(authorizationManager).isInstanceOf(AuthenticatedAuthorizationManager.class);
+	}
+
+	@Test
+	public void rememberMeReturnsAuthenticatedAuthorizationManagerByDefault() {
+		AuthorizationManagerFactory<String> factory = new DefaultAuthorizationManagerFactory<>();
+		AuthorizationManager<String> authorizationManager = factory.rememberMe();
+		assertThat(authorizationManager).isInstanceOf(AuthenticatedAuthorizationManager.class);
+	}
+
+	@Test
+	public void anonymousReturnsAuthenticatedAuthorizationManagerByDefault() {
+		AuthorizationManagerFactory<String> factory = new DefaultAuthorizationManagerFactory<>();
+		AuthorizationManager<String> authorizationManager = factory.anonymous();
+		assertThat(authorizationManager).isInstanceOf(AuthenticatedAuthorizationManager.class);
+	}
+
+}

+ 53 - 11
data/src/main/java/org/springframework/security/data/repository/query/SecurityEvaluationContextExtension.java

@@ -26,6 +26,8 @@ import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
 import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
 import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
 import org.springframework.security.authentication.AuthenticationTrustResolver;
 import org.springframework.security.authentication.AuthenticationTrustResolver;
 import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
 import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
+import org.springframework.security.authorization.AuthorizationManagerFactory;
+import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.context.SecurityContextHolder;
@@ -92,18 +94,18 @@ import org.springframework.util.Assert;
  */
  */
 public class SecurityEvaluationContextExtension implements EvaluationContextExtension {
 public class SecurityEvaluationContextExtension implements EvaluationContextExtension {
 
 
+	private static final String DEFAULT_ROLE_PREFIX = "ROLE_";
+
 	private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
 	private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
 		.getContextHolderStrategy();
 		.getContextHolderStrategy();
 
 
 	private @Nullable Authentication authentication;
 	private @Nullable Authentication authentication;
 
 
-	private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
-
-	private RoleHierarchy roleHierarchy = new NullRoleHierarchy();
+	private AuthorizationManagerFactory<Object> authorizationManagerFactory = new DefaultAuthorizationManagerFactory<>();
 
 
 	private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator();
 	private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator();
 
 
-	private String defaultRolePrefix = "ROLE_";
+	private String defaultRolePrefix = DEFAULT_ROLE_PREFIX;
 
 
 	/**
 	/**
 	 * Creates a new instance that uses the current {@link Authentication} found on the
 	 * Creates a new instance that uses the current {@link Authentication} found on the
@@ -126,14 +128,16 @@ public class SecurityEvaluationContextExtension implements EvaluationContextExte
 	}
 	}
 
 
 	@Override
 	@Override
-	public SecurityExpressionRoot getRootObject() {
+	public SecurityExpressionRoot<Object> getRootObject() {
 		Authentication authentication = getAuthentication();
 		Authentication authentication = getAuthentication();
-		SecurityExpressionRoot root = new SecurityExpressionRoot(authentication) {
+		SecurityExpressionRoot<Object> root = new SecurityExpressionRoot<>(() -> authentication, new Object()) {
 		};
 		};
-		root.setTrustResolver(this.trustResolver);
-		root.setRoleHierarchy(this.roleHierarchy);
+		root.setAuthorizationManagerFactory(this.authorizationManagerFactory);
 		root.setPermissionEvaluator(this.permissionEvaluator);
 		root.setPermissionEvaluator(this.permissionEvaluator);
-		root.setDefaultRolePrefix(this.defaultRolePrefix);
+		if (!DEFAULT_ROLE_PREFIX.equals(this.defaultRolePrefix)) {
+			// Ensure SecurityExpressionRoot can strip the custom role prefix
+			root.setDefaultRolePrefix(this.defaultRolePrefix);
+		}
 		return root;
 		return root;
 	}
 	}
 
 
@@ -156,15 +160,46 @@ public class SecurityEvaluationContextExtension implements EvaluationContextExte
 		return context.getAuthentication();
 		return context.getAuthentication();
 	}
 	}
 
 
+	/**
+	 * Sets the {@link AuthorizationManagerFactory} to be used. The default is
+	 * {@link DefaultAuthorizationManagerFactory}.
+	 * @param authorizationManagerFactory the {@link AuthorizationManagerFactory} to use.
+	 * Cannot be null.
+	 * @since 7.0
+	 */
+	public void setAuthorizationManagerFactory(AuthorizationManagerFactory<Object> authorizationManagerFactory) {
+		Assert.notNull(authorizationManagerFactory, "authorizationManagerFactory cannot be null");
+		this.authorizationManagerFactory = authorizationManagerFactory;
+	}
+
+	/**
+	 * Allows accessing the {@link DefaultAuthorizationManagerFactory} for getting and
+	 * setting defaults. This method will be removed in Spring Security 8.
+	 * @return the {@link DefaultAuthorizationManagerFactory}
+	 * @throws IllegalStateException if a different {@link AuthorizationManagerFactory}
+	 * was already set
+	 */
+	private DefaultAuthorizationManagerFactory<Object> getDefaultAuthorizationManagerFactory() {
+		if (!(this.authorizationManagerFactory instanceof DefaultAuthorizationManagerFactory<Object> defaultAuthorizationManagerFactory)) {
+			throw new IllegalStateException(
+					"authorizationManagerFactory must be an instance of DefaultAuthorizationManagerFactory");
+		}
+
+		return defaultAuthorizationManagerFactory;
+	}
+
 	/**
 	/**
 	 * Sets the {@link AuthenticationTrustResolver} to be used. Default is
 	 * Sets the {@link AuthenticationTrustResolver} to be used. Default is
 	 * {@link AuthenticationTrustResolverImpl}. Cannot be null.
 	 * {@link AuthenticationTrustResolverImpl}. Cannot be null.
 	 * @param trustResolver the {@link AuthenticationTrustResolver} to use
 	 * @param trustResolver the {@link AuthenticationTrustResolver} to use
 	 * @since 5.8
 	 * @since 5.8
+	 * @deprecated Use
+	 * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
 	 */
 	 */
+	@Deprecated(since = "7.0")
 	public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
 	public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
 		Assert.notNull(trustResolver, "trustResolver cannot be null");
 		Assert.notNull(trustResolver, "trustResolver cannot be null");
-		this.trustResolver = trustResolver;
+		getDefaultAuthorizationManagerFactory().setTrustResolver(trustResolver);
 	}
 	}
 
 
 	/**
 	/**
@@ -172,10 +207,13 @@ public class SecurityEvaluationContextExtension implements EvaluationContextExte
 	 * Cannot be null.
 	 * Cannot be null.
 	 * @param roleHierarchy the {@link RoleHierarchy} to use
 	 * @param roleHierarchy the {@link RoleHierarchy} to use
 	 * @since 5.8
 	 * @since 5.8
+	 * @deprecated Use
+	 * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
 	 */
 	 */
+	@Deprecated(since = "7.0")
 	public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
 	public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
 		Assert.notNull(roleHierarchy, "roleHierarchy cannot be null");
 		Assert.notNull(roleHierarchy, "roleHierarchy cannot be null");
-		this.roleHierarchy = roleHierarchy;
+		getDefaultAuthorizationManagerFactory().setRoleHierarchy(roleHierarchy);
 	}
 	}
 
 
 	/**
 	/**
@@ -199,8 +237,12 @@ public class SecurityEvaluationContextExtension implements EvaluationContextExte
 	 * @param defaultRolePrefix the default prefix to add to roles. The default is
 	 * @param defaultRolePrefix the default prefix to add to roles. The default is
 	 * "ROLE_".
 	 * "ROLE_".
 	 * @since 5.8
 	 * @since 5.8
+	 * @deprecated Use
+	 * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
 	 */
 	 */
+	@Deprecated(since = "7.0")
 	public void setDefaultRolePrefix(String defaultRolePrefix) {
 	public void setDefaultRolePrefix(String defaultRolePrefix) {
+		getDefaultAuthorizationManagerFactory().setRolePrefix(defaultRolePrefix);
 		this.defaultRolePrefix = defaultRolePrefix;
 		this.defaultRolePrefix = defaultRolePrefix;
 	}
 	}
 
 

+ 16 - 15
data/src/test/java/org/springframework/security/data/repository/query/SecurityEvaluationContextExtensionTests.java

@@ -23,10 +23,9 @@ import org.junit.jupiter.api.Test;
 import org.springframework.security.access.PermissionEvaluator;
 import org.springframework.security.access.PermissionEvaluator;
 import org.springframework.security.access.expression.DenyAllPermissionEvaluator;
 import org.springframework.security.access.expression.DenyAllPermissionEvaluator;
 import org.springframework.security.access.expression.SecurityExpressionRoot;
 import org.springframework.security.access.expression.SecurityExpressionRoot;
-import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
 import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
 import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
+import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
 import org.springframework.security.authentication.AuthenticationTrustResolver;
 import org.springframework.security.authentication.AuthenticationTrustResolver;
-import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
 import org.springframework.security.authentication.TestingAuthenticationToken;
 import org.springframework.security.authentication.TestingAuthenticationToken;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.context.SecurityContextHolderStrategy;
 import org.springframework.security.core.context.SecurityContextHolderStrategy;
@@ -102,9 +101,11 @@ public class SecurityEvaluationContextExtensionTests {
 	public void setTrustResolverWhenNotNullThenVerifyRootObject() {
 	public void setTrustResolverWhenNotNullThenVerifyRootObject() {
 		TestingAuthenticationToken explicit = new TestingAuthenticationToken("explicit", "password", "ROLE_EXPLICIT");
 		TestingAuthenticationToken explicit = new TestingAuthenticationToken("explicit", "password", "ROLE_EXPLICIT");
 		this.securityExtension = new SecurityEvaluationContextExtension(explicit);
 		this.securityExtension = new SecurityEvaluationContextExtension(explicit);
-		AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
+		AuthenticationTrustResolver trustResolver = mock(AuthenticationTrustResolver.class);
+		given(trustResolver.isAuthenticated(explicit)).willReturn(true);
 		this.securityExtension.setTrustResolver(trustResolver);
 		this.securityExtension.setTrustResolver(trustResolver);
-		assertThat(getRoot()).extracting("trustResolver").isEqualTo(trustResolver);
+		assertThat(getRoot().isAuthenticated()).isTrue();
+		verify(trustResolver).isAuthenticated(explicit);
 	}
 	}
 
 
 	@Test
 	@Test
@@ -117,11 +118,11 @@ public class SecurityEvaluationContextExtensionTests {
 
 
 	@Test
 	@Test
 	public void setRoleHierarchyWhenNotNullThenVerifyRootObject() {
 	public void setRoleHierarchyWhenNotNullThenVerifyRootObject() {
-		TestingAuthenticationToken explicit = new TestingAuthenticationToken("explicit", "password", "ROLE_EXPLICIT");
+		TestingAuthenticationToken explicit = new TestingAuthenticationToken("explicit", "password", "ROLE_PARENT");
 		this.securityExtension = new SecurityEvaluationContextExtension(explicit);
 		this.securityExtension = new SecurityEvaluationContextExtension(explicit);
-		RoleHierarchy roleHierarchy = new NullRoleHierarchy();
+		RoleHierarchy roleHierarchy = RoleHierarchyImpl.fromHierarchy("ROLE_PARENT > ROLE_EXPLICIT");
 		this.securityExtension.setRoleHierarchy(roleHierarchy);
 		this.securityExtension.setRoleHierarchy(roleHierarchy);
-		assertThat(getRoot()).extracting("roleHierarchy").isEqualTo(roleHierarchy);
+		assertThat(getRoot().hasRole("EXPLICIT")).isTrue();
 	}
 	}
 
 
 	@Test
 	@Test
@@ -143,25 +144,25 @@ public class SecurityEvaluationContextExtensionTests {
 
 
 	@Test
 	@Test
 	public void setDefaultRolePrefixWhenCustomThenVerifyRootObject() {
 	public void setDefaultRolePrefixWhenCustomThenVerifyRootObject() {
-		TestingAuthenticationToken explicit = new TestingAuthenticationToken("explicit", "password", "ROLE_EXPLICIT");
+		TestingAuthenticationToken explicit = new TestingAuthenticationToken("explicit", "password", "CUSTOM_EXPLICIT");
 		this.securityExtension = new SecurityEvaluationContextExtension(explicit);
 		this.securityExtension = new SecurityEvaluationContextExtension(explicit);
 		String defaultRolePrefix = "CUSTOM_";
 		String defaultRolePrefix = "CUSTOM_";
 		this.securityExtension.setDefaultRolePrefix(defaultRolePrefix);
 		this.securityExtension.setDefaultRolePrefix(defaultRolePrefix);
-		assertThat(getRoot()).extracting("defaultRolePrefix").isEqualTo(defaultRolePrefix);
+		assertThat(getRoot().hasRole("EXPLICIT")).isTrue();
 	}
 	}
 
 
 	@Test
 	@Test
 	public void getRootObjectWhenAdditionalFieldsNotSetThenVerifyDefaults() {
 	public void getRootObjectWhenAdditionalFieldsNotSetThenVerifyDefaults() {
 		TestingAuthenticationToken explicit = new TestingAuthenticationToken("explicit", "password", "ROLE_EXPLICIT");
 		TestingAuthenticationToken explicit = new TestingAuthenticationToken("explicit", "password", "ROLE_EXPLICIT");
 		this.securityExtension = new SecurityEvaluationContextExtension(explicit);
 		this.securityExtension = new SecurityEvaluationContextExtension(explicit);
-		SecurityExpressionRoot root = getRoot();
-		assertThat(root).extracting("trustResolver").isInstanceOf(AuthenticationTrustResolverImpl.class);
-		assertThat(root).extracting("roleHierarchy").isInstanceOf(NullRoleHierarchy.class);
-		assertThat(root).extracting("permissionEvaluator").isInstanceOf(DenyAllPermissionEvaluator.class);
-		assertThat(root).extracting("defaultRolePrefix").isEqualTo("ROLE_");
+		SecurityExpressionRoot<?> securityExpressionRoot = getRoot();
+		assertThat(securityExpressionRoot.isAuthenticated()).isTrue();
+		assertThat(securityExpressionRoot.hasRole("PARENT")).isFalse();
+		assertThat(securityExpressionRoot.hasRole("EXPLICIT")).isTrue();
+		assertThat(securityExpressionRoot.hasPermission(new Object(), "read")).isFalse();
 	}
 	}
 
 
-	private SecurityExpressionRoot getRoot() {
+	private SecurityExpressionRoot<?> getRoot() {
 		return this.securityExtension.getRootObject();
 		return this.securityExtension.getRootObject();
 	}
 	}
 
 

+ 37 - 0
docs/modules/ROOT/pages/servlet/authorization/architecture.adoc

@@ -144,6 +144,43 @@ Another manager is the `AuthenticatedAuthorizationManager`.
 It can be used to differentiate between anonymous, fully-authenticated and remember-me authenticated users.
 It can be used to differentiate between anonymous, fully-authenticated and remember-me authenticated users.
 Many sites allow certain limited access under remember-me authentication, but require a user to confirm their identity by logging in for full access.
 Many sites allow certain limited access under remember-me authentication, but require a user to confirm their identity by logging in for full access.
 
 
+[[authz-authorization-manager-factory]]
+=== Creating AuthorizationManager instances
+
+The javadoc:org.springframework.security.authorization.AuthorizationManagerFactory[] interface (introduced in Spring Security 7.0) is used to create generic ``AuthorizationManager``s in xref:servlet/authorization/authorize-http-requests.adoc[request-based] and xref:servlet/authorization/method-security.adoc[method-based] authorization components.
+The following is a sketch of the `AuthorizationManagerFactory` interface:
+
+[source,java]
+----
+public interface AuthorizationManagerFactory<T> {
+	AuthorizationManager<T> permitAll();
+	AuthorizationManager<T> denyAll();
+	AuthorizationManager<T> hasRole(String role);
+	AuthorizationManager<T> hasAnyRole(String... roles);
+	AuthorizationManager<T> hasAuthority(String authority);
+	AuthorizationManager<T> hasAnyAuthority(String... authorities);
+	AuthorizationManager<T> authenticated();
+	AuthorizationManager<T> fullyAuthenticated();
+	AuthorizationManager<T> rememberMe();
+	AuthorizationManager<T> anonymous();
+}
+----
+
+The default implementation is javadoc:org.springframework.security.authorization.DefaultAuthorizationManagerFactory[], which allows for customizing the `rolePrefix` (defaults to `"ROLE_"`), `RoleHierarchy` and `AuthenticationTrustManager` that are provided to the ``AuthorizationManager``s created by the factory.
+
+In order to customize the default instance used by Spring Security, simply publish a bean as in the following example:
+
+include-code::./AuthorizationManagerFactoryConfiguration[tag=config,indent=0]
+
+[TIP]
+It is also possible to target a specific usage of this factory within Spring Security by providing a concrete parameterized type instead of a generic type.
+See examples of each in the xref:servlet/authorization/authorize-http-requests.adoc#customizing-authorization-managers[request-based] and xref:servlet/authorization/method-security.adoc#customizing-authorization-managers[method-based] sections of the documentation.
+
+In addition to simply customizing the default instance of `AuthorizationManagerFactory`, you can provide your own implementation to fully customize the instances created by the factory and provide your own implementations.
+
+[NOTE]
+The {gh-url}/core/src/main/java/org/springframework/security/authorization/AuthorizationManagerFactory.java[actual interface] provides default implementations for all factory methods, which allows custom implementations to only implement the methods that need to be customized.
+
 [[authz-authorization-managers]]
 [[authz-authorization-managers]]
 ==== AuthorizationManagers
 ==== AuthorizationManagers
 There are also helpful static factories in javadoc:org.springframework.security.authorization.AuthorizationManagers[] for composing individual ``AuthorizationManager``s into more sophisticated expressions.
 There are also helpful static factories in javadoc:org.springframework.security.authorization.AuthorizationManagers[] for composing individual ``AuthorizationManager``s into more sophisticated expressions.

+ 19 - 0
docs/modules/ROOT/pages/servlet/authorization/authorize-http-requests.adoc

@@ -57,6 +57,7 @@ In many cases, your authorization rules will be more sophisticated than that, so
 * I want to <<match-by-custom, match a request programmatically>>
 * I want to <<match-by-custom, match a request programmatically>>
 * I want to <<authorize-requests, authorize a request programmatically>>
 * I want to <<authorize-requests, authorize a request programmatically>>
 * I want to <<remote-authorization-manager, delegate request authorization>> to a policy agent
 * I want to <<remote-authorization-manager, delegate request authorization>> to a policy agent
+* I want to <<customizing-authorization-managers,customize how authorization managers are created>>
 
 
 [[request-authorization-architecture]]
 [[request-authorization-architecture]]
 == Understanding How Request Authorization Components Work
 == Understanding How Request Authorization Components Work
@@ -765,6 +766,24 @@ You will notice that since we are using the `hasRole` expression we do not need
 <6> Any URL that has not already been matched on is denied access.
 <6> Any URL that has not already been matched on is denied access.
 This is a good strategy if you do not want to accidentally forget to update your authorization rules.
 This is a good strategy if you do not want to accidentally forget to update your authorization rules.
 
 
+[[customizing-authorization-managers]]
+== Customizing Authorization Managers
+
+When you use the `authorizeHttpRequests` DSL, Spring Security takes care of creating the appropriate `AuthorizationManager` instances for you.
+In certain cases, you may want to customize what is created in order to have complete control over how authorization decisions are made xref:servlet/authorization/architecture.adoc#authz-delegate-authorization-manager[at the framework level].
+
+In order to take control of creating instances of `AuthorizationManager` for authorizing HTTP requests, you can create a custom xref:servlet/authorization/architecture.adoc#authz-authorization-manager-factory[`AuthorizationManagerFactory`].
+For example, let's say you want to create a convention that authenticated users must be authenticated _AND_ have the `USER` role.
+To do this, you can create a custom implementation for HTTP requests as in the following example:
+
+include-code::./CustomHttpRequestsAuthorizationManagerFactory[tag=class,indent=0]
+
+Now, whenever you <<activate-request-security,require authentication>>, Spring Security will automatically invoke your custom factory to create an instance of `AuthorizationManager` that requires authentication _AND_ the `USER` role.
+
+[TIP]
+We use this as a simple example of creating a custom `AuthorizationManagerFactory`, though it is also possible (and often simpler) to replace a specific `AuthorizationManager` only for a particular request.
+See <<remote-authorization-manager>> for an example.
+
 [[authorization-expressions]]
 [[authorization-expressions]]
 == Expressing Authorization with SpEL
 == Expressing Authorization with SpEL
 
 

+ 18 - 0
docs/modules/ROOT/pages/servlet/authorization/method-security.adoc

@@ -1995,6 +1995,24 @@ This works on both classes and interfaces.
 This does not work for interfaces, since they do not have debug information about the parameter names.
 This does not work for interfaces, since they do not have debug information about the parameter names.
 For interfaces, either annotations or the `-parameters` approach must be used.
 For interfaces, either annotations or the `-parameters` approach must be used.
 
 
+[[customizing-authorization-managers]]
+== Customizing Authorization Managers
+
+When you use SpEL expressions with <<use-preauthorize,`@PreAuthorize`>>, <<use-postauthorize,`@PostAuthorize`>>, <<use-prefilter,`@PreFilter`>> and <<use-postfilter,`@PostFilter`>>, Spring Security takes care of creating the appropriate `AuthorizationManager` instances for you.
+In certain cases, you may want to customize what is created in order to have complete control over how authorization decisions are made xref:servlet/authorization/architecture.adoc#authz-delegate-authorization-manager[at the framework level].
+
+In order to take control of creating instances of `AuthorizationManager` for pre- and post-annotations, you can create a custom xref:servlet/authorization/architecture.adoc#authz-authorization-manager-factory[`AuthorizationManagerFactory`].
+For example, let's say you want to allow users with the `ADMIN` role whenever any other role is required.
+To do this, you can create a custom implementation for method security as in the following example:
+
+include-code::./CustomMethodInvocationAuthorizationManagerFactory[tag=class,indent=0]
+
+Now, whenever you <<use-preauthorize,use the `@PreAuthorize` annotation>> with `hasRole` or `hasAnyRole`, Spring Security will automatically invoke your custom factory to create an instance of `AuthorizationManager` that allows access for the given role(s) _OR_ the `ADMIN` role.
+
+[TIP]
+We use this as a simple example of creating a custom `AuthorizationManagerFactory`, though the same outcome could be accomplished with <<favor-granting-authorities,a role hierarchy>>.
+Use whichever approach fits best in your situation.
+
 [[authorize-object]]
 [[authorize-object]]
 == Authorizing Arbitrary Objects
 == Authorizing Arbitrary Objects
 
 

+ 1 - 0
docs/modules/ROOT/pages/whats-new.adoc

@@ -12,6 +12,7 @@ Each section that follows will indicate the more notable removals as well as the
 == Core
 == Core
 
 
 * Removed `AuthorizationManager#check` in favor of `AuthorizationManager#authorize`
 * Removed `AuthorizationManager#check` in favor of `AuthorizationManager#authorize`
+* Added xref:servlet/authorization/architecture.adoc#authz-authorization-manager-factory[`AuthorizationManagerFactory`] for creating `AuthorizationManager` instances in xref:servlet/authorization/authorize-http-requests.adoc#customizing-authorization-managers[request-based] and xref:servlet/authorization/method-security.adoc#customizing-authorization-managers[method-based] authorization components
 
 
 == Config
 == Config
 
 

+ 77 - 0
docs/src/test/java/org/springframework/security/docs/servlet/authorization/authzauthorizationmanagerfactory/AuthorizationManagerFactoryConfiguration.java

@@ -0,0 +1,77 @@
+/*
+ * 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 clients 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.docs.servlet.authorization.authzauthorizationmanagerfactory;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
+import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
+import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.authorization.AuthorizationManagerFactory;
+import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
+
+/**
+ * Documentation for {@link AuthorizationManagerFactory}.
+ *
+ * @author Steve Riesenberg
+ */
+@Configuration(proxyBeanMethods = false)
+public class AuthorizationManagerFactoryConfiguration {
+
+	// tag::config[]
+	@Bean
+	<T> AuthorizationManagerFactory<T> authorizationManagerFactory() {
+		DefaultAuthorizationManagerFactory<T> authorizationManagerFactory =
+				new DefaultAuthorizationManagerFactory<>();
+		authorizationManagerFactory.setTrustResolver(getAuthenticationTrustResolver());
+		authorizationManagerFactory.setRoleHierarchy(getRoleHierarchy());
+		authorizationManagerFactory.setRolePrefix("role_");
+
+		return authorizationManagerFactory;
+	}
+	// end::config[]
+
+	private static AuthenticationTrustResolverImpl getAuthenticationTrustResolver() {
+		AuthenticationTrustResolverImpl authenticationTrustResolver =
+				new AuthenticationTrustResolverImpl();
+		authenticationTrustResolver.setAnonymousClass(Anonymous.class);
+		authenticationTrustResolver.setRememberMeClass(RememberMe.class);
+
+		return authenticationTrustResolver;
+	}
+
+	private static RoleHierarchyImpl getRoleHierarchy() {
+		return RoleHierarchyImpl.fromHierarchy("role_admin > role_user");
+	}
+
+	static class Anonymous extends TestingAuthenticationToken {
+
+		Anonymous(String principal) {
+			super(principal, "", "role_anonymous");
+		}
+
+	}
+
+	static class RememberMe extends TestingAuthenticationToken {
+
+		RememberMe(String principal) {
+			super(principal, "", "role_rememberMe");
+		}
+
+	}
+
+}

+ 208 - 0
docs/src/test/java/org/springframework/security/docs/servlet/authorization/authzauthorizationmanagerfactory/AuthorizationManagerFactoryConfigurationTests.java

@@ -0,0 +1,208 @@
+/*
+ * 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 clients 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.docs.servlet.authorization.authzauthorizationmanagerfactory;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.test.SpringTestContext;
+import org.springframework.security.config.test.SpringTestContextExtension;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.docs.servlet.authorization.authzauthorizationmanagerfactory.AuthorizationManagerFactoryConfiguration.Anonymous;
+import org.springframework.security.docs.servlet.authorization.authzauthorizationmanagerfactory.AuthorizationManagerFactoryConfiguration.RememberMe;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
+import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+/**
+ * Tests for {@link AuthorizationManagerFactoryConfiguration}.
+ *
+ * @author Steve Riesenberg
+ */
+@ExtendWith(SpringTestContextExtension.class)
+public class AuthorizationManagerFactoryConfigurationTests {
+
+	public final SpringTestContext spring = new SpringTestContext(this);
+
+	@Autowired
+	MockMvc mockMvc;
+
+	@Test
+	void getAnonymousWhenCustomAnonymousClassThenOk() throws Exception {
+		this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
+				TestController.class).autowire();
+		Authentication authentication = new Anonymous("anonymous");
+		// @formatter:off
+		this.mockMvc.perform(get("/anonymous").with(authentication(authentication)))
+			.andExpect(status().isOk())
+			.andExpect(authenticated().withAuthentication(authentication));
+		// @formatter:on
+	}
+
+	@Test
+	void getAnonymousWhenAuthenticatedThenForbidden() throws Exception {
+		this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
+				TestController.class).autowire();
+		Authentication authentication = new TestingAuthenticationToken("user", "", "role_user");
+		// @formatter:off
+		this.mockMvc.perform(get("/anonymous").with(authentication(authentication)))
+			.andExpect(status().isForbidden())
+			.andExpect(authenticated().withAuthentication(authentication));
+		// @formatter:on
+	}
+
+	@Test
+	void getRememberMeWhenCustomRememberMeClassThenOk() throws Exception {
+		this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
+				TestController.class).autowire();
+		Authentication authentication = new RememberMe("rememberMe");
+		// @formatter:off
+		this.mockMvc.perform(get("/rememberMe").with(authentication(authentication)))
+			.andExpect(status().isOk())
+			.andExpect(authenticated().withAuthentication(authentication));
+		// @formatter:on
+	}
+
+	@Test
+	void getRememberMeWhenAuthenticatedThenForbidden() throws Exception {
+		this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
+				TestController.class).autowire();
+		Authentication authentication = new TestingAuthenticationToken("user", "", "role_user");
+		// @formatter:off
+		this.mockMvc.perform(get("/rememberMe").with(authentication(authentication)))
+			.andExpect(status().isForbidden())
+			.andExpect(authenticated().withAuthentication(authentication));
+		// @formatter:on
+	}
+
+	@Test
+	void getUserWhenCustomUserRoleThenOk() throws Exception {
+		this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
+				TestController.class).autowire();
+		Authentication authentication = new TestingAuthenticationToken("user", "", "role_user");
+		// @formatter:off
+		this.mockMvc.perform(get("/user").with(authentication(authentication)))
+			.andExpect(status().isOk())
+			.andExpect(authenticated().withAuthentication(authentication));
+		// @formatter:on
+	}
+
+	@Test
+	void getUserWhenCustomAdminRoleThenOk() throws Exception {
+		this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
+				TestController.class).autowire();
+		Authentication authentication = new TestingAuthenticationToken("admin", "", "role_admin");
+		// @formatter:off
+		this.mockMvc.perform(get("/user").with(authentication(authentication)))
+			.andExpect(status().isOk())
+			.andExpect(authenticated().withAuthentication(authentication));
+		// @formatter:on
+	}
+
+	@Test
+	void getPreAuthorizeWhenCustomUserRoleThenOk() throws Exception {
+		this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
+				TestController.class).autowire();
+		Authentication authentication = new TestingAuthenticationToken("user", "", "role_user");
+		// @formatter:off
+		this.mockMvc.perform(get("/preAuthorize").with(authentication(authentication)))
+			.andExpect(status().isOk())
+			.andExpect(authenticated().withAuthentication(authentication));
+		// @formatter:on
+	}
+
+	@Test
+	void getPreAuthorizeWhenCustomAdminRoleThenOk() throws Exception {
+		this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
+				TestController.class).autowire();
+		Authentication authentication = new TestingAuthenticationToken("admin", "", "role_admin");
+		// @formatter:off
+		this.mockMvc.perform(get("/preAuthorize").with(authentication(authentication)))
+			.andExpect(status().isOk())
+			.andExpect(authenticated().withAuthentication(authentication));
+		// @formatter:on
+	}
+
+	@Test
+	void getPreAuthorizeWhenOtherRoleThenForbidden() throws Exception {
+		this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
+				TestController.class).autowire();
+		Authentication authentication = new TestingAuthenticationToken("other", "", "role_other");
+		// @formatter:off
+		this.mockMvc.perform(get("/preAuthorize").with(authentication(authentication)))
+			.andExpect(status().isForbidden())
+			.andExpect(authenticated().withAuthentication(authentication));
+		// @formatter:on
+	}
+
+	@EnableWebMvc
+	@EnableWebSecurity
+	@EnableMethodSecurity
+	@Configuration
+	static class SecurityConfiguration {
+
+		@Bean
+		SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.authorizeHttpRequests((authorize) -> authorize
+					.requestMatchers("/anonymous").anonymous()
+					.requestMatchers("/rememberMe").rememberMe()
+					.requestMatchers("/user").hasRole("user")
+					.requestMatchers("/preAuthorize").permitAll()
+					.anyRequest().denyAll()
+				);
+			// @formatter:on
+			return http.build();
+		}
+
+	}
+
+	@RestController
+	static class TestController {
+
+		@GetMapping({ "/anonymous", "/rememberMe", "/user" })
+		@ResponseStatus(HttpStatus.OK)
+		void httpRequest() {
+		}
+
+		@GetMapping("/preAuthorize")
+		@ResponseStatus(HttpStatus.OK)
+		@PreAuthorize("hasRole('user')")
+		void preAuthorize() {
+		}
+
+	}
+
+}

+ 48 - 0
docs/src/test/java/org/springframework/security/docs/servlet/authorization/customizingauthorizationmanagers/CustomHttpRequestsAuthorizationManagerFactory.java

@@ -0,0 +1,48 @@
+/*
+ * 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 clients 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.docs.servlet.authorization.customizingauthorizationmanagers;
+
+import org.springframework.security.authorization.AuthorizationManager;
+import org.springframework.security.authorization.AuthorizationManagerFactory;
+import org.springframework.security.authorization.AuthorizationManagers;
+import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
+import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
+import org.springframework.stereotype.Component;
+
+/**
+ * Documentation for {@link AuthorizationManagerFactory}.
+ *
+ * @author Steve Riesenberg
+ */
+// tag::class[]
+@Component
+public class CustomHttpRequestsAuthorizationManagerFactory
+		implements AuthorizationManagerFactory<RequestAuthorizationContext> {
+
+	private final AuthorizationManagerFactory<RequestAuthorizationContext> delegate =
+			new DefaultAuthorizationManagerFactory<>();
+
+	@Override
+	public AuthorizationManager<RequestAuthorizationContext> authenticated() {
+		return AuthorizationManagers.allOf(
+			this.delegate.authenticated(),
+			this.delegate.hasRole("USER")
+		);
+	}
+
+}
+// end::class[]

+ 137 - 0
docs/src/test/java/org/springframework/security/docs/servlet/authorization/customizingauthorizationmanagers/CustomHttpRequestsAuthorizationManagerFactoryTests.java

@@ -0,0 +1,137 @@
+/*
+ * 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 clients 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.docs.servlet.authorization.customizingauthorizationmanagers;
+
+import java.util.Collections;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.test.SpringTestContext;
+import org.springframework.security.config.test.SpringTestContextExtension;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
+import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
+import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+/**
+ * Tests for {@link CustomHttpRequestsAuthorizationManagerFactory}.
+ *
+ * @author Steve Riesenberg
+ */
+@ExtendWith(SpringTestContextExtension.class)
+public class CustomHttpRequestsAuthorizationManagerFactoryTests {
+
+	public final SpringTestContext spring = new SpringTestContext(this);
+
+	@Autowired
+	MockMvc mockMvc;
+
+	@Test
+	void getHelloWhenAnonymousThenForbidden() throws Exception {
+		this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
+		// @formatter:off
+		this.mockMvc.perform(get("/hello").with(anonymous()))
+			.andExpect(status().isForbidden())
+			.andExpect(unauthenticated());
+		// @formatter:on
+	}
+
+	@Test
+	void getHelloWhenAuthenticatedWithNoRolesThenForbidden() throws Exception {
+		this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
+		Authentication authentication = new TestingAuthenticationToken("user", "", Collections.emptyList());
+		// @formatter:off
+		this.mockMvc.perform(get("/hello").with(authentication(authentication)))
+			.andExpect(status().isForbidden())
+			.andExpect(authenticated().withAuthentication(authentication));
+		// @formatter:on
+	}
+
+	@Test
+	void getHelloWhenAuthenticatedWithUserRoleThenOk() throws Exception {
+		this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
+		Authentication authentication = new TestingAuthenticationToken("user", "", "ROLE_USER");
+		// @formatter:off
+		this.mockMvc.perform(get("/hello").with(authentication(authentication)))
+			.andExpect(status().isOk())
+			.andExpect(authenticated().withAuthentication(authentication));
+		// @formatter:on
+	}
+
+	@Test
+	void getHelloWhenAuthenticatedWithOtherRoleThenForbidden() throws Exception {
+		this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
+		Authentication authentication = new TestingAuthenticationToken("user", "", "ROLE_OTHER");
+		// @formatter:off
+		this.mockMvc.perform(get("/hello").with(authentication(authentication)))
+			.andExpect(status().isForbidden())
+			.andExpect(authenticated().withAuthentication(authentication));
+		// @formatter:on
+	}
+
+	@EnableWebMvc
+	@EnableWebSecurity
+	@Configuration
+	static class SecurityConfiguration {
+
+		@Bean
+		SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.authorizeHttpRequests((authorize) -> authorize
+					.anyRequest().authenticated()
+				);
+			// @formatter:on
+			return http.build();
+		}
+
+		@Bean
+		CustomHttpRequestsAuthorizationManagerFactory customHttpRequestsAuthorizationManagerFactory() {
+			return new CustomHttpRequestsAuthorizationManagerFactory();
+		}
+
+	}
+
+	@RestController
+	static class TestController {
+
+		@GetMapping("/**")
+		@ResponseStatus(HttpStatus.OK)
+		void ok() {
+		}
+
+	}
+
+}

+ 57 - 0
docs/src/test/java/org/springframework/security/docs/servlet/authorization/customizingauthorizationmanagers/CustomMethodInvocationAuthorizationManagerFactory.java

@@ -0,0 +1,57 @@
+/*
+ * 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 clients 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.docs.servlet.authorization.customizingauthorizationmanagers;
+
+import org.aopalliance.intercept.MethodInvocation;
+
+import org.springframework.security.authorization.AuthorizationManager;
+import org.springframework.security.authorization.AuthorizationManagerFactory;
+import org.springframework.security.authorization.AuthorizationManagers;
+import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
+import org.springframework.stereotype.Component;
+
+/**
+ * Documentation for {@link AuthorizationManagerFactory}.
+ *
+ * @author Steve Riesenberg
+ */
+// tag::class[]
+@Component
+public class CustomMethodInvocationAuthorizationManagerFactory
+		implements AuthorizationManagerFactory<MethodInvocation> {
+
+	private final AuthorizationManagerFactory<MethodInvocation> delegate =
+			new DefaultAuthorizationManagerFactory<>();
+
+	@Override
+	public AuthorizationManager<MethodInvocation> hasRole(String role) {
+		return AuthorizationManagers.anyOf(
+			this.delegate.hasRole(role),
+			this.delegate.hasRole("ADMIN")
+		);
+	}
+
+	@Override
+	public AuthorizationManager<MethodInvocation> hasAnyRole(String... roles) {
+		return AuthorizationManagers.anyOf(
+			this.delegate.hasAnyRole(roles),
+			this.delegate.hasRole("ADMIN")
+		);
+	}
+
+}
+// end::class[]

+ 191 - 0
docs/src/test/java/org/springframework/security/docs/servlet/authorization/customizingauthorizationmanagers/CustomMethodInvocationAuthorizationManagerFactoryTests.java

@@ -0,0 +1,191 @@
+/*
+ * 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 clients 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.docs.servlet.authorization.customizingauthorizationmanagers;
+
+import java.util.Collections;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.test.SpringTestContext;
+import org.springframework.security.config.test.SpringTestContextExtension;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
+import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
+import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+/**
+ * Tests for {@link CustomMethodInvocationAuthorizationManagerFactory}.
+ *
+ * @author Steve Riesenberg
+ */
+@ExtendWith(SpringTestContextExtension.class)
+public class CustomMethodInvocationAuthorizationManagerFactoryTests {
+
+	public final SpringTestContext spring = new SpringTestContext(this);
+
+	@Autowired
+	MockMvc mockMvc;
+
+	@Test
+	void getUserWhenAnonymousThenForbidden() throws Exception {
+		this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
+		// @formatter:off
+		this.mockMvc.perform(get("/user").with(anonymous()))
+			.andExpect(status().isForbidden())
+			.andExpect(unauthenticated());
+		// @formatter:on
+	}
+
+	@Test
+	void getUserWhenAuthenticatedWithNoRolesThenForbidden() throws Exception {
+		this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
+		Authentication authentication = new TestingAuthenticationToken("user", "", Collections.emptyList());
+		// @formatter:off
+		this.mockMvc.perform(get("/user").with(authentication(authentication)))
+			.andExpect(status().isForbidden())
+			.andExpect(authenticated().withAuthentication(authentication));
+		// @formatter:on
+	}
+
+	@Test
+	void getUserWhenAuthenticatedWithUserRoleThenOk() throws Exception {
+		this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
+		Authentication authentication = new TestingAuthenticationToken("user", "", "ROLE_USER");
+		// @formatter:off
+		this.mockMvc.perform(get("/user").with(authentication(authentication)))
+			.andExpect(status().isOk())
+			.andExpect(authenticated().withAuthentication(authentication));
+		// @formatter:on
+	}
+
+	@Test
+	void getUserWhenAuthenticatedWithAdminRoleThenOk() throws Exception {
+		this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
+		Authentication authentication = new TestingAuthenticationToken("admin", "", "ROLE_ADMIN");
+		// @formatter:off
+		this.mockMvc.perform(get("/user").with(authentication(authentication)))
+			.andExpect(status().isOk())
+			.andExpect(authenticated().withAuthentication(authentication));
+		// @formatter:on
+	}
+
+	@Test
+	void getUserWhenAuthenticatedWithOtherRoleThenForbidden() throws Exception {
+		this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
+		Authentication authentication = new TestingAuthenticationToken("user", "", "ROLE_OTHER");
+		// @formatter:off
+		this.mockMvc.perform(get("/user").with(authentication(authentication)))
+			.andExpect(status().isForbidden())
+			.andExpect(authenticated().withAuthentication(authentication));
+		// @formatter:on
+	}
+
+	@Test
+	void getRolesWhenAuthenticatedWithRole1RoleThenOk() throws Exception {
+		this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
+		Authentication authentication = new TestingAuthenticationToken("user", "", "ROLE_ROLE1");
+		// @formatter:off
+		this.mockMvc.perform(get("/roles").with(authentication(authentication)))
+			.andExpect(status().isOk())
+			.andExpect(authenticated().withAuthentication(authentication));
+		// @formatter:on
+	}
+
+	@Test
+	void getRolesWhenAuthenticatedWithAdminRoleThenOk() throws Exception {
+		this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
+		Authentication authentication = new TestingAuthenticationToken("admin", "", "ROLE_ADMIN");
+		// @formatter:off
+		this.mockMvc.perform(get("/roles").with(authentication(authentication)))
+			.andExpect(status().isOk())
+			.andExpect(authenticated().withAuthentication(authentication));
+		// @formatter:on
+	}
+
+	@Test
+	void getRolesWhenAuthenticatedWithOtherRoleThenForbidden() throws Exception {
+		this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
+		Authentication authentication = new TestingAuthenticationToken("user", "", "ROLE_OTHER");
+		// @formatter:off
+		this.mockMvc.perform(get("/roles").with(authentication(authentication)))
+			.andExpect(status().isForbidden())
+			.andExpect(authenticated().withAuthentication(authentication));
+		// @formatter:on
+	}
+
+	@EnableWebMvc
+	@EnableWebSecurity
+	@EnableMethodSecurity
+	@Configuration
+	static class SecurityConfiguration {
+
+		@Bean
+		SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.authorizeHttpRequests((authorize) -> authorize
+					.anyRequest().authenticated()
+				);
+			// @formatter:on
+			return http.build();
+		}
+
+		@Bean
+		CustomMethodInvocationAuthorizationManagerFactory customMethodInvocationAuthorizationManagerFactory() {
+			return new CustomMethodInvocationAuthorizationManagerFactory();
+		}
+
+	}
+
+	@RestController
+	static class TestController {
+
+		@GetMapping("/user")
+		@ResponseStatus(HttpStatus.OK)
+		@PreAuthorize("hasRole('USER')")
+		void user() {
+		}
+
+		@GetMapping("/roles")
+		@ResponseStatus(HttpStatus.OK)
+		@PreAuthorize("hasAnyRole('ROLE1', 'ROLE2')")
+		void roles() {
+		}
+
+	}
+
+}

+ 62 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authorization/authzauthorizationmanagerfactory/AuthorizationManagerFactoryConfiguration.kt

@@ -0,0 +1,62 @@
+/*
+ * 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.kt.docs.servlet.authorization.authzauthorizationmanagerfactory
+
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl
+import org.springframework.security.authentication.AuthenticationTrustResolverImpl
+import org.springframework.security.authentication.TestingAuthenticationToken
+import org.springframework.security.authorization.AuthorizationManagerFactory
+import org.springframework.security.authorization.DefaultAuthorizationManagerFactory
+
+/**
+ * Documentation for [org.springframework.security.authorization.AuthorizationManagerFactory].
+ *
+ * @author Steve Riesenberg
+ */
+@Configuration(proxyBeanMethods = false)
+class AuthorizationManagerFactoryConfiguration {
+    // tag::config[]
+    @Bean
+    fun <T> authorizationManagerFactory(): AuthorizationManagerFactory<T> {
+        val authorizationManagerFactory = DefaultAuthorizationManagerFactory<T>()
+        authorizationManagerFactory.setTrustResolver(getAuthenticationTrustResolver())
+        authorizationManagerFactory.setRoleHierarchy(getRoleHierarchy())
+        authorizationManagerFactory.setRolePrefix("role_")
+
+        return authorizationManagerFactory
+    }
+    // end::config[]
+
+    private fun getAuthenticationTrustResolver(): AuthenticationTrustResolverImpl {
+        val authenticationTrustResolver = AuthenticationTrustResolverImpl()
+        authenticationTrustResolver.setAnonymousClass(Anonymous::class.java)
+        authenticationTrustResolver.setRememberMeClass(RememberMe::class.java)
+
+        return authenticationTrustResolver
+    }
+
+    private fun getRoleHierarchy(): RoleHierarchyImpl {
+        return RoleHierarchyImpl.fromHierarchy("role_admin > role_user")
+    }
+
+    internal class Anonymous(principal: String) :
+        TestingAuthenticationToken(principal, "", "role_anonymous")
+
+    internal class RememberMe(principal: String) :
+        TestingAuthenticationToken(principal, "", "role_rememberMe")
+}

+ 229 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authorization/authzauthorizationmanagerfactory/AuthorizationManagerFactoryConfigurationTests.kt

@@ -0,0 +1,229 @@
+/*
+ * 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.kt.docs.servlet.authorization.authzauthorizationmanagerfactory
+
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.http.HttpStatus
+import org.springframework.security.access.prepost.PreAuthorize
+import org.springframework.security.authentication.TestingAuthenticationToken
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.test.SpringTestContext
+import org.springframework.security.config.test.SpringTestContextExtension
+import org.springframework.security.kt.docs.servlet.authorization.authzauthorizationmanagerfactory.AuthorizationManagerFactoryConfiguration.Anonymous
+import org.springframework.security.kt.docs.servlet.authorization.authzauthorizationmanagerfactory.AuthorizationManagerFactoryConfiguration.RememberMe
+import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication
+import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated
+import org.springframework.security.web.SecurityFilterChain
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.ResponseStatus
+import org.springframework.web.bind.annotation.RestController
+import org.springframework.web.servlet.config.annotation.EnableWebMvc
+
+/**
+ * Tests for [AuthorizationManagerFactoryConfiguration].
+ *
+ * @author Steve Riesenberg
+ */
+@ExtendWith(SpringTestContextExtension::class)
+class AuthorizationManagerFactoryConfigurationTests {
+    @JvmField
+    val spring = SpringTestContext(this)
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    @Throws(Exception::class)
+    fun getAnonymousWhenCustomAnonymousClassThenOk() {
+        this.spring.register(AuthorizationManagerFactoryConfiguration::class.java, SecurityConfiguration::class.java)
+            .autowire()
+        val authentication = Anonymous("anonymous")
+        // @formatter:off
+        mockMvc.perform(get("/anonymous").with(authentication(authentication)))
+            .andExpect(status().isOk())
+            .andExpect(authenticated().withAuthentication(authentication))
+        // @formatter:on
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun getAnonymousWhenAuthenticatedThenForbidden() {
+        this.spring.register(AuthorizationManagerFactoryConfiguration::class.java, SecurityConfiguration::class.java)
+            .autowire()
+        val authentication = TestingAuthenticationToken("user", "", "role_user")
+        // @formatter:off
+        mockMvc.perform(get("/anonymous").with(authentication(authentication)))
+            .andExpect(status().isForbidden())
+            .andExpect(authenticated().withAuthentication(authentication))
+        // @formatter:on
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun getRememberMeWhenCustomRememberMeClassThenOk() {
+        this.spring.register(AuthorizationManagerFactoryConfiguration::class.java, SecurityConfiguration::class.java)
+            .autowire()
+        val authentication = RememberMe("rememberMe")
+        // @formatter:off
+        mockMvc.perform(get("/rememberMe").with(authentication(authentication)))
+            .andExpect(status().isOk())
+            .andExpect(authenticated().withAuthentication(authentication))
+        // @formatter:on
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun getRememberMeWhenAuthenticatedThenForbidden() {
+        this.spring.register(AuthorizationManagerFactoryConfiguration::class.java, SecurityConfiguration::class.java)
+            .autowire()
+        val user = TestingAuthenticationToken("user", "", "role_user")
+        // @formatter:off
+        mockMvc.perform(get("/rememberMe").with(authentication(user)))
+            .andExpect(status().isForbidden())
+            .andExpect(authenticated().withAuthentication(user))
+        // @formatter:on
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun getUserWhenCustomUserRoleThenOk() {
+        this.spring.register(AuthorizationManagerFactoryConfiguration::class.java, SecurityConfiguration::class.java)
+            .autowire()
+        val authentication = TestingAuthenticationToken("user", "", "role_user")
+        // @formatter:off
+        mockMvc.perform(get("/user").with(authentication(authentication)))
+            .andExpect(status().isOk())
+            .andExpect(authenticated().withAuthentication(authentication))
+        // @formatter:on
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun getUserWhenCustomAdminRoleThenOk() {
+        this.spring.register(AuthorizationManagerFactoryConfiguration::class.java, SecurityConfiguration::class.java)
+            .autowire()
+        val admin = TestingAuthenticationToken("admin", "", "role_admin")
+        // @formatter:off
+        mockMvc.perform(get("/user").with(authentication(admin)))
+            .andExpect(status().isOk())
+            .andExpect(authenticated().withAuthentication(admin))
+        // @formatter:on
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun getPreAuthorizeWhenCustomUserRoleThenOk() {
+        this.spring.register(AuthorizationManagerFactoryConfiguration::class.java, SecurityConfiguration::class.java)
+            .autowire()
+        val authentication = TestingAuthenticationToken("user", "", "role_user")
+        // @formatter:off
+        mockMvc.perform(get("/preAuthorize").with(authentication(authentication)))
+            .andExpect(status().isOk())
+            .andExpect(authenticated().withAuthentication(authentication))
+        // @formatter:on
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun getPreAuthorizeWhenCustomAdminRoleThenOk() {
+        this.spring.register(AuthorizationManagerFactoryConfiguration::class.java, SecurityConfiguration::class.java)
+            .autowire()
+        val authentication = TestingAuthenticationToken("admin", "", "role_admin")
+        // @formatter:off
+        mockMvc.perform(get("/preAuthorize").with(authentication(authentication)))
+            .andExpect(status().isOk())
+            .andExpect(authenticated().withAuthentication(authentication))
+        // @formatter:on
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun getPreAuthorizeWhenOtherRoleThenForbidden() {
+        this.spring.register(AuthorizationManagerFactoryConfiguration::class.java, SecurityConfiguration::class.java)
+            .autowire()
+        val authentication = TestingAuthenticationToken("other", "", "role_other")
+        // @formatter:off
+        mockMvc.perform(get("/preAuthorize").with(authentication(authentication)))
+            .andExpect(status().isForbidden())
+            .andExpect(authenticated().withAuthentication(authentication))
+        // @formatter:on
+    }
+
+    @EnableWebMvc
+    @EnableWebSecurity
+    @EnableMethodSecurity
+    @Configuration
+    internal open class SecurityConfiguration {
+        @Bean
+        @Throws(Exception::class)
+        open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
+            // @formatter:off
+            http.authorizeHttpRequests { authorize ->
+                authorize
+                    .requestMatchers("/anonymous").anonymous()
+                    .requestMatchers("/rememberMe").rememberMe()
+                    .requestMatchers("/user").hasRole("user")
+                    .requestMatchers("/preAuthorize").permitAll()
+                    .anyRequest().denyAll()
+            }
+            // @formatter:on
+            return http.build()
+        }
+
+        @Bean
+        open fun testController(testService: TestService): TestController {
+            return TestController(testService())
+        }
+
+        @Bean
+        open fun testService(): TestService {
+            return TestServiceImpl()
+        }
+    }
+
+    @RestController
+    internal open class TestController(private val testService: TestService) {
+        @GetMapping(value = ["/anonymous", "/rememberMe", "/user"])
+        @ResponseStatus(HttpStatus.OK)
+        fun httpRequest() {
+        }
+
+        @GetMapping("/preAuthorize")
+        @ResponseStatus(HttpStatus.OK)
+        fun preAuthorize() {
+            testService.preAuthorize()
+        }
+    }
+
+    internal interface TestService {
+        @PreAuthorize("hasRole('user')")
+        fun preAuthorize()
+    }
+
+    internal open class TestServiceImpl : TestService {
+        override fun preAuthorize() {
+        }
+    }
+}

+ 44 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authorization/customizingauthorizationmanagers/CustomHttpRequestsAuthorizationManagerFactory.kt

@@ -0,0 +1,44 @@
+/*
+ * 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.kt.docs.servlet.authorization.customizingauthorizationmanagers
+
+import org.springframework.security.authorization.AuthorizationManager
+import org.springframework.security.authorization.AuthorizationManagerFactory
+import org.springframework.security.authorization.AuthorizationManagers
+import org.springframework.security.authorization.DefaultAuthorizationManagerFactory
+import org.springframework.security.web.access.intercept.RequestAuthorizationContext
+import org.springframework.stereotype.Component
+
+/**
+ * Documentation for {@link AuthorizationManagerFactory}.
+ *
+ * @author Steve Riesenberg
+ */
+// tag::class[]
+@Component
+class CustomHttpRequestsAuthorizationManagerFactory : AuthorizationManagerFactory<RequestAuthorizationContext> {
+    private val delegate = DefaultAuthorizationManagerFactory<RequestAuthorizationContext>()
+
+    override fun authenticated(): AuthorizationManager<RequestAuthorizationContext> {
+        return AuthorizationManagers.allOf(
+            delegate.authenticated(),
+            delegate.hasRole("USER")
+        )
+    }
+}
+// end::class[]
+
+

+ 131 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authorization/customizingauthorizationmanagers/CustomHttpRequestsAuthorizationManagerFactoryTests.kt

@@ -0,0 +1,131 @@
+/*
+ * 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.kt.docs.servlet.authorization.customizingauthorizationmanagers
+
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.http.HttpStatus
+import org.springframework.security.authentication.TestingAuthenticationToken
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.test.SpringTestContext
+import org.springframework.security.config.test.SpringTestContextExtension
+import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous
+import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication
+import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers
+import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated
+import org.springframework.security.web.SecurityFilterChain
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.ResponseStatus
+import org.springframework.web.bind.annotation.RestController
+import org.springframework.web.servlet.config.annotation.EnableWebMvc
+
+/**
+ * Tests for [CustomHttpRequestsAuthorizationManagerFactory].
+ *
+ * @author Steve Riesenberg
+ */
+@ExtendWith(SpringTestContextExtension::class)
+class CustomHttpRequestsAuthorizationManagerFactoryTests {
+    @JvmField
+    val spring = SpringTestContext(this)
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    @Throws(Exception::class)
+    fun getHelloWhenAnonymousThenForbidden() {
+        spring.register(SecurityConfiguration::class.java, TestController::class.java).autowire()
+        // @formatter:off
+        mockMvc.perform(get("/hello").with(anonymous()))
+            .andExpect(status().isForbidden())
+            .andExpect(SecurityMockMvcResultMatchers.unauthenticated())
+        // @formatter:on
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun getHelloWhenAuthenticatedWithUserRoleThenOk() {
+        spring.register(SecurityConfiguration::class.java, TestController::class.java).autowire()
+        val authentication = TestingAuthenticationToken("user", "", "ROLE_USER")
+        // @formatter:off
+        mockMvc.perform(get("/hello").with(authentication(authentication)))
+            .andExpect(status().isOk())
+            .andExpect(authenticated().withAuthentication(authentication))
+        // @formatter:on
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun getHelloWhenAuthenticatedWithOtherRoleThenForbidden() {
+        spring.register(SecurityConfiguration::class.java, TestController::class.java).autowire()
+        val authentication = TestingAuthenticationToken("user", "", "ROLE_OTHER")
+        // @formatter:off
+        mockMvc.perform(get("/hello").with(authentication(authentication)))
+            .andExpect(status().isForbidden())
+            .andExpect(authenticated().withAuthentication(authentication))
+        // @formatter:on
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun getHelloWhenAuthenticatedWithNoRolesThenForbidden() {
+        spring.register(SecurityConfiguration::class.java, TestController::class.java).autowire()
+        val authentication = TestingAuthenticationToken("user", "", listOf())
+        // @formatter:off
+        mockMvc.perform(get("/hello").with(authentication(authentication)))
+            .andExpect(status().isForbidden())
+            .andExpect(authenticated().withAuthentication(authentication))
+        // @formatter:on
+    }
+
+    @EnableWebMvc
+    @EnableWebSecurity
+    @Configuration
+    internal open class SecurityConfiguration {
+        @Bean
+        @Throws(Exception::class)
+        open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
+            // @formatter:off
+            http
+                .authorizeHttpRequests { authorize ->
+                    authorize.anyRequest().authenticated()
+                }
+            // @formatter:on
+            return http.build()
+        }
+
+        @Bean
+        open fun customHttpRequestsAuthorizationManagerFactory(): CustomHttpRequestsAuthorizationManagerFactory {
+            return CustomHttpRequestsAuthorizationManagerFactory()
+        }
+    }
+
+    @RestController
+    internal class TestController {
+        @GetMapping("/**")
+        @ResponseStatus(HttpStatus.OK)
+        fun ok() {
+        }
+    }
+}

+ 50 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authorization/customizingauthorizationmanagers/CustomMethodInvocationAuthorizationManagerFactory.kt

@@ -0,0 +1,50 @@
+/*
+ * 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.kt.docs.servlet.authorization.customizingauthorizationmanagers
+
+import org.aopalliance.intercept.MethodInvocation
+import org.springframework.security.authorization.AuthorizationManager
+import org.springframework.security.authorization.AuthorizationManagerFactory
+import org.springframework.security.authorization.AuthorizationManagers
+import org.springframework.security.authorization.DefaultAuthorizationManagerFactory
+import org.springframework.stereotype.Component
+
+/**
+ * Documentation for [AuthorizationManagerFactory].
+ *
+ * @author Steve Riesenberg
+ */
+// tag::class[]
+@Component
+class CustomMethodInvocationAuthorizationManagerFactory : AuthorizationManagerFactory<MethodInvocation> {
+    private val delegate = DefaultAuthorizationManagerFactory<MethodInvocation>()
+
+    override fun hasRole(role: String): AuthorizationManager<MethodInvocation> {
+        return AuthorizationManagers.anyOf(
+            delegate.hasRole(role),
+            delegate.hasRole("ADMIN")
+        )
+    }
+
+    override fun hasAnyRole(vararg roles: String): AuthorizationManager<MethodInvocation> {
+        return AuthorizationManagers.anyOf(
+            delegate.hasAnyRole(*roles),
+            delegate.hasRole("ADMIN")
+        )
+    }
+}
+// end::class[]
+

+ 215 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authorization/customizingauthorizationmanagers/CustomMethodInvocationAuthorizationManagerFactoryTests.kt

@@ -0,0 +1,215 @@
+/*
+ * 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.kt.docs.servlet.authorization.customizingauthorizationmanagers
+
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.http.HttpStatus
+import org.springframework.security.access.prepost.PreAuthorize
+import org.springframework.security.authentication.TestingAuthenticationToken
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.test.SpringTestContext
+import org.springframework.security.config.test.SpringTestContextExtension
+import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous
+import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication
+import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated
+import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated
+import org.springframework.security.web.SecurityFilterChain
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.ResponseStatus
+import org.springframework.web.bind.annotation.RestController
+import org.springframework.web.servlet.config.annotation.EnableWebMvc
+
+/**
+ * Tests for [CustomMethodInvocationAuthorizationManagerFactory].
+ *
+ * @author Steve Riesenberg
+ */
+@ExtendWith(SpringTestContextExtension::class)
+class CustomMethodInvocationAuthorizationManagerFactoryTests {
+    @JvmField
+    val spring = SpringTestContext(this)
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    @Throws(Exception::class)
+    fun getUserWhenAnonymousThenForbidden() {
+        spring.register(SecurityConfiguration::class.java).autowire()
+        // @formatter:off
+        mockMvc.perform(get("/user").with(anonymous()))
+            .andExpect(status().isForbidden())
+            .andExpect(unauthenticated())
+        // @formatter:on
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun getUserWhenAuthenticatedWithNoRolesThenForbidden() {
+        spring.register(SecurityConfiguration::class.java).autowire()
+        val authentication = TestingAuthenticationToken("user", "", listOf())
+        // @formatter:off
+        mockMvc.perform(get("/user").with(authentication(authentication)))
+            .andExpect(status().isForbidden())
+            .andExpect(authenticated().withAuthentication(authentication))
+        // @formatter:on
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun getUserWhenAuthenticatedWithUserRoleThenOk() {
+        spring.register(SecurityConfiguration::class.java).autowire()
+        val authentication = TestingAuthenticationToken("user", "", "ROLE_USER")
+        // @formatter:off
+        mockMvc.perform(get("/user").with(authentication(authentication)))
+            .andExpect(status().isOk())
+            .andExpect(authenticated().withAuthentication(authentication))
+        // @formatter:on
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun getUserWhenAuthenticatedWithAdminRoleThenOk() {
+        spring.register(SecurityConfiguration::class.java).autowire()
+        val authentication = TestingAuthenticationToken("user", "", "ROLE_ADMIN")
+        // @formatter:off
+        mockMvc.perform(get("/user").with(authentication(authentication)))
+            .andExpect(status().isOk())
+            .andExpect(authenticated().withAuthentication(authentication))
+        // @formatter:on
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun getUserWhenAuthenticatedWithOtherRoleThenForbidden() {
+        spring.register(SecurityConfiguration::class.java).autowire()
+        val authentication = TestingAuthenticationToken("user", "", "ROLE_OTHER")
+        // @formatter:off
+        mockMvc.perform(get("/user").with(authentication(authentication)))
+            .andExpect(status().isForbidden())
+            .andExpect(authenticated().withAuthentication(authentication))
+        // @formatter:on
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun getRolesWhenAuthenticatedWithRole1RoleThenOk() {
+        spring.register(SecurityConfiguration::class.java).autowire()
+        val authentication = TestingAuthenticationToken("user", "", "ROLE_ROLE1")
+        // @formatter:off
+        mockMvc.perform(get("/roles").with(authentication(authentication)))
+            .andExpect(status().isOk())
+            .andExpect(authenticated().withAuthentication(authentication))
+        // @formatter:on
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun getRolesWhenAuthenticatedWithAdminRoleThenOk() {
+        spring.register(SecurityConfiguration::class.java).autowire()
+        val authentication = TestingAuthenticationToken("user", "", "ROLE_ADMIN")
+        // @formatter:off
+        mockMvc.perform(get("/roles").with(authentication(authentication)))
+            .andExpect(status().isOk())
+            .andExpect(authenticated().withAuthentication(authentication))
+        // @formatter:on
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun getRolesWhenAuthenticatedWithOtherRoleThenForbidden() {
+        spring.register(SecurityConfiguration::class.java).autowire()
+        val authentication = TestingAuthenticationToken("user", "", "ROLE_OTHER")
+        // @formatter:off
+        mockMvc.perform(get("/roles").with(authentication(authentication)))
+            .andExpect(status().isForbidden())
+            .andExpect(authenticated().withAuthentication(authentication))
+        // @formatter:on
+    }
+
+    @EnableWebMvc
+    @EnableWebSecurity
+    @EnableMethodSecurity
+    @Configuration
+    internal open class SecurityConfiguration {
+        @Bean
+        @Throws(Exception::class)
+        open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
+            // @formatter:off
+            http
+                .authorizeHttpRequests { authorize ->
+                    authorize.anyRequest().authenticated()
+                }
+            // @formatter:on
+            return http.build()
+        }
+
+        @Bean
+        open fun customMethodInvocationAuthorizationManagerFactory(): CustomMethodInvocationAuthorizationManagerFactory {
+            return CustomMethodInvocationAuthorizationManagerFactory()
+        }
+
+        @Bean
+        open fun testController(testService: TestService): TestController {
+            return TestController(testService())
+        }
+
+        @Bean
+        open fun testService(): TestService {
+            return TestServiceImpl()
+        }
+    }
+
+    @RestController
+    internal open class TestController(private val testService: TestService) {
+        @GetMapping("/user")
+        @ResponseStatus(HttpStatus.OK)
+        fun user() {
+            testService.user()
+        }
+
+        @GetMapping("/roles")
+        @ResponseStatus(HttpStatus.OK)
+        fun roles() {
+            testService.roles()
+        }
+    }
+
+    internal interface TestService {
+        @PreAuthorize("hasRole('USER')")
+        fun user()
+
+        @PreAuthorize("hasAnyRole('ROLE1', 'ROLE2')")
+        fun roles()
+    }
+
+    internal open class TestServiceImpl : TestService {
+        override fun user() {
+        }
+
+        override fun roles() {
+        }
+    }
+}

+ 40 - 0
docs/src/test/resources/org/springframework/security/docs/servlet/authorization/authzauthorizationmanagerfactory/AuthorizationManagerFactoryConfiguration.xml

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xsi:schemaLocation="
+			http://www.springframework.org/schema/beans
+			https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<!-- tag::config[] -->
+	<b:bean id="authorizationManagerFactory" class="org.springframework.security.authorization.DefaultAuthorizationManagerFactory">
+		<b:property name="trustResolver" ref="authenticationTrustResolver"/>
+		<b:property name="roleHierarchy" ref="roleHierarchy"/>
+		<b:property name="rolePrefix" value="role_"/>
+	</b:bean>
+	<!-- end::config[] -->
+
+	<b:bean id="authenticationTrustResolver" class="org.springframework.security.authentication.AuthenticationTrustResolverImpl">
+		<b:property name="anonymousClass" value="org.springframework.security.authentication.TestingAuthenticationToken"/>
+	</b:bean>
+
+	<b:bean id="roleHierarchy" class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl" factory-method="fromHierarchy">
+		<b:constructor-arg name="hierarchy" value="role_admin > role_user"/>
+	</b:bean>
+
+</b:beans>

+ 1 - 0
etc/checkstyle/checkstyle-suppressions.xml

@@ -36,6 +36,7 @@
 	<suppress files="JoseHeader\.java" checks="SpringMethodVisibility"/>
 	<suppress files="JoseHeader\.java" checks="SpringMethodVisibility"/>
 	<suppress files="DefaultLoginPageGeneratingFilterTests\.java" checks="SpringLeadingWhitespace"/>
 	<suppress files="DefaultLoginPageGeneratingFilterTests\.java" checks="SpringLeadingWhitespace"/>
 	<suppress files="AuthenticationException\.java" checks="MutableException"/>
 	<suppress files="AuthenticationException\.java" checks="MutableException"/>
+	<suppress files="FilterInvocationExpressionRoot\.java" checks="SpringMethodVisibility"/>
 
 
 	<!-- Lambdas that we can't replace with a method reference because a closure is required -->
 	<!-- Lambdas that we can't replace with a method reference because a closure is required -->
 	<suppress files="BearerTokenAuthenticationFilter\.java" checks="SpringLambda"/>
 	<suppress files="BearerTokenAuthenticationFilter\.java" checks="SpringLambda"/>

+ 11 - 11
messaging/src/main/java/org/springframework/security/messaging/access/expression/DefaultMessageSecurityExpressionHandler.java

@@ -28,9 +28,8 @@ import org.springframework.security.access.expression.AbstractSecurityExpression
 import org.springframework.security.access.expression.SecurityExpressionHandler;
 import org.springframework.security.access.expression.SecurityExpressionHandler;
 import org.springframework.security.access.expression.SecurityExpressionOperations;
 import org.springframework.security.access.expression.SecurityExpressionOperations;
 import org.springframework.security.authentication.AuthenticationTrustResolver;
 import org.springframework.security.authentication.AuthenticationTrustResolver;
-import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
+import org.springframework.security.authorization.AuthorizationManagerFactory;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.Authentication;
-import org.springframework.util.Assert;
 
 
 /**
 /**
  * The default implementation of {@link SecurityExpressionHandler} which uses a
  * The default implementation of {@link SecurityExpressionHandler} which uses a
@@ -43,12 +42,10 @@ import org.springframework.util.Assert;
  */
  */
 public class DefaultMessageSecurityExpressionHandler<T> extends AbstractSecurityExpressionHandler<Message<T>> {
 public class DefaultMessageSecurityExpressionHandler<T> extends AbstractSecurityExpressionHandler<Message<T>> {
 
 
-	private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
-
 	@Override
 	@Override
 	public EvaluationContext createEvaluationContext(Supplier<? extends @Nullable Authentication> authentication,
 	public EvaluationContext createEvaluationContext(Supplier<? extends @Nullable Authentication> authentication,
 			Message<T> message) {
 			Message<T> message) {
-		MessageSecurityExpressionRoot root = createSecurityExpressionRoot(authentication, message);
+		MessageSecurityExpressionRoot<T> root = createSecurityExpressionRoot(authentication, message);
 		StandardEvaluationContext ctx = new StandardEvaluationContext(root);
 		StandardEvaluationContext ctx = new StandardEvaluationContext(root);
 		BeanResolver beanResolver = getBeanResolver();
 		BeanResolver beanResolver = getBeanResolver();
 		if (beanResolver != null) {
 		if (beanResolver != null) {
@@ -64,18 +61,21 @@ public class DefaultMessageSecurityExpressionHandler<T> extends AbstractSecurity
 		return createSecurityExpressionRoot(() -> authentication, invocation);
 		return createSecurityExpressionRoot(() -> authentication, invocation);
 	}
 	}
 
 
-	private MessageSecurityExpressionRoot createSecurityExpressionRoot(
+	private MessageSecurityExpressionRoot<T> createSecurityExpressionRoot(
 			Supplier<? extends Authentication> authentication, Message<T> invocation) {
 			Supplier<? extends Authentication> authentication, Message<T> invocation) {
-		MessageSecurityExpressionRoot root = new MessageSecurityExpressionRoot(authentication, invocation);
+		MessageSecurityExpressionRoot<T> root = new MessageSecurityExpressionRoot<>(authentication, invocation);
+		root.setAuthorizationManagerFactory(getAuthorizationManagerFactory());
 		root.setPermissionEvaluator(getPermissionEvaluator());
 		root.setPermissionEvaluator(getPermissionEvaluator());
-		root.setTrustResolver(this.trustResolver);
-		root.setRoleHierarchy(getRoleHierarchy());
 		return root;
 		return root;
 	}
 	}
 
 
+	/**
+	 * @deprecated Use
+	 * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
+	 */
+	@Deprecated(since = "7.0")
 	public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
 	public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
-		Assert.notNull(trustResolver, "trustResolver cannot be null");
-		this.trustResolver = trustResolver;
+		getDefaultAuthorizationManagerFactory().setTrustResolver(trustResolver);
 	}
 	}
 
 
 }
 }

+ 9 - 5
messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageSecurityExpressionRoot.java

@@ -18,6 +18,8 @@ package org.springframework.security.messaging.access.expression;
 
 
 import java.util.function.Supplier;
 import java.util.function.Supplier;
 
 
+import org.jspecify.annotations.Nullable;
+
 import org.springframework.messaging.Message;
 import org.springframework.messaging.Message;
 import org.springframework.security.access.expression.SecurityExpressionRoot;
 import org.springframework.security.access.expression.SecurityExpressionRoot;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.Authentication;
@@ -27,13 +29,14 @@ import org.springframework.security.core.Authentication;
  *
  *
  * @author Rob Winch
  * @author Rob Winch
  * @author Evgeniy Cheban
  * @author Evgeniy Cheban
+ * @author Steve Riesenberg
  * @since 4.0
  * @since 4.0
  */
  */
-public class MessageSecurityExpressionRoot extends SecurityExpressionRoot {
+public class MessageSecurityExpressionRoot<T> extends SecurityExpressionRoot<Message<T>> {
 
 
-	public final Message<?> message;
+	public final Message<T> message;
 
 
-	public MessageSecurityExpressionRoot(Authentication authentication, Message<?> message) {
+	public MessageSecurityExpressionRoot(Authentication authentication, Message<T> message) {
 		this(() -> authentication, message);
 		this(() -> authentication, message);
 	}
 	}
 
 
@@ -44,8 +47,9 @@ public class MessageSecurityExpressionRoot extends SecurityExpressionRoot {
 	 * @param message the {@link Message} to use
 	 * @param message the {@link Message} to use
 	 * @since 5.8
 	 * @since 5.8
 	 */
 	 */
-	public MessageSecurityExpressionRoot(Supplier<? extends Authentication> authentication, Message<?> message) {
-		super(authentication);
+	public MessageSecurityExpressionRoot(Supplier<? extends @Nullable Authentication> authentication,
+			Message<T> message) {
+		super(authentication, message);
 		this.message = message;
 		this.message = message;
 	}
 	}
 
 

+ 18 - 8
web/src/main/java/org/springframework/security/web/access/expression/DefaultHttpSecurityExpressionHandler.java

@@ -27,6 +27,7 @@ import org.springframework.security.access.expression.SecurityExpressionHandler;
 import org.springframework.security.access.expression.SecurityExpressionOperations;
 import org.springframework.security.access.expression.SecurityExpressionOperations;
 import org.springframework.security.authentication.AuthenticationTrustResolver;
 import org.springframework.security.authentication.AuthenticationTrustResolver;
 import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
 import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
+import org.springframework.security.authorization.AuthorizationManagerFactory;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
 import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
 import org.springframework.util.Assert;
 import org.springframework.util.Assert;
@@ -36,14 +37,15 @@ import org.springframework.util.Assert;
  * create a {@link WebSecurityExpressionRoot}.
  * create a {@link WebSecurityExpressionRoot}.
  *
  *
  * @author Evgeniy Cheban
  * @author Evgeniy Cheban
+ * @author Steve Riesenberg
  * @since 5.8
  * @since 5.8
  */
  */
 public class DefaultHttpSecurityExpressionHandler extends AbstractSecurityExpressionHandler<RequestAuthorizationContext>
 public class DefaultHttpSecurityExpressionHandler extends AbstractSecurityExpressionHandler<RequestAuthorizationContext>
 		implements SecurityExpressionHandler<RequestAuthorizationContext> {
 		implements SecurityExpressionHandler<RequestAuthorizationContext> {
 
 
-	private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
+	private static final String DEFAULT_ROLE_PREFIX = "ROLE_";
 
 
-	private String defaultRolePrefix = "ROLE_";
+	private String defaultRolePrefix = DEFAULT_ROLE_PREFIX;
 
 
 	@Override
 	@Override
 	@SuppressWarnings("NullAway") // https://github.com/spring-projects/spring-framework/issues/35371
 	@SuppressWarnings("NullAway") // https://github.com/spring-projects/spring-framework/issues/35371
@@ -64,11 +66,13 @@ public class DefaultHttpSecurityExpressionHandler extends AbstractSecurityExpres
 
 
 	private WebSecurityExpressionRoot createSecurityExpressionRoot(
 	private WebSecurityExpressionRoot createSecurityExpressionRoot(
 			Supplier<? extends @Nullable Authentication> authentication, RequestAuthorizationContext context) {
 			Supplier<? extends @Nullable Authentication> authentication, RequestAuthorizationContext context) {
-		WebSecurityExpressionRoot root = new WebSecurityExpressionRoot(authentication, context.getRequest());
-		root.setRoleHierarchy(getRoleHierarchy());
+		WebSecurityExpressionRoot root = new WebSecurityExpressionRoot(authentication, context);
+		root.setAuthorizationManagerFactory(getAuthorizationManagerFactory());
 		root.setPermissionEvaluator(getPermissionEvaluator());
 		root.setPermissionEvaluator(getPermissionEvaluator());
-		root.setTrustResolver(this.trustResolver);
-		root.setDefaultRolePrefix(this.defaultRolePrefix);
+		if (!DEFAULT_ROLE_PREFIX.equals(this.defaultRolePrefix)) {
+			// Ensure SecurityExpressionRoot can strip the custom role prefix
+			root.setDefaultRolePrefix(this.defaultRolePrefix);
+		}
 		return root;
 		return root;
 	}
 	}
 
 
@@ -76,10 +80,12 @@ public class DefaultHttpSecurityExpressionHandler extends AbstractSecurityExpres
 	 * Sets the {@link AuthenticationTrustResolver} to be used. The default is
 	 * Sets the {@link AuthenticationTrustResolver} to be used. The default is
 	 * {@link AuthenticationTrustResolverImpl}.
 	 * {@link AuthenticationTrustResolverImpl}.
 	 * @param trustResolver the {@link AuthenticationTrustResolver} to use
 	 * @param trustResolver the {@link AuthenticationTrustResolver} to use
+	 * @deprecated Use
+	 * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
 	 */
 	 */
+	@Deprecated(since = "7.0")
 	public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
 	public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
-		Assert.notNull(trustResolver, "trustResolver cannot be null");
-		this.trustResolver = trustResolver;
+		getDefaultAuthorizationManagerFactory().setTrustResolver(trustResolver);
 	}
 	}
 
 
 	/**
 	/**
@@ -91,9 +97,13 @@ public class DefaultHttpSecurityExpressionHandler extends AbstractSecurityExpres
 	 * role ROLE_ADMIN will be used when the defaultRolePrefix is "ROLE_" (default).
 	 * role ROLE_ADMIN will be used when the defaultRolePrefix is "ROLE_" (default).
 	 * @param defaultRolePrefix the default prefix to add to roles. The default is
 	 * @param defaultRolePrefix the default prefix to add to roles. The default is
 	 * "ROLE_".
 	 * "ROLE_".
+	 * @deprecated Use
+	 * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
 	 */
 	 */
+	@Deprecated(since = "7.0")
 	public void setDefaultRolePrefix(String defaultRolePrefix) {
 	public void setDefaultRolePrefix(String defaultRolePrefix) {
 		Assert.notNull(defaultRolePrefix, "defaultRolePrefix cannot be null");
 		Assert.notNull(defaultRolePrefix, "defaultRolePrefix cannot be null");
+		getDefaultAuthorizationManagerFactory().setRolePrefix(defaultRolePrefix);
 		this.defaultRolePrefix = defaultRolePrefix;
 		this.defaultRolePrefix = defaultRolePrefix;
 	}
 	}
 
 

+ 22 - 10
web/src/main/java/org/springframework/security/web/access/expression/DefaultWebSecurityExpressionHandler.java

@@ -23,30 +23,33 @@ import org.springframework.security.access.expression.SecurityExpressionHandler;
 import org.springframework.security.access.expression.SecurityExpressionOperations;
 import org.springframework.security.access.expression.SecurityExpressionOperations;
 import org.springframework.security.authentication.AuthenticationTrustResolver;
 import org.springframework.security.authentication.AuthenticationTrustResolver;
 import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
 import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
+import org.springframework.security.authorization.AuthorizationManagerFactory;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.web.FilterInvocation;
 import org.springframework.security.web.FilterInvocation;
-import org.springframework.util.Assert;
 
 
 /**
 /**
  * @author Luke Taylor
  * @author Luke Taylor
  * @author Eddú Meléndez
  * @author Eddú Meléndez
+ * @author Steve Riesenberg
  * @since 3.0
  * @since 3.0
  */
  */
 public class DefaultWebSecurityExpressionHandler extends AbstractSecurityExpressionHandler<FilterInvocation>
 public class DefaultWebSecurityExpressionHandler extends AbstractSecurityExpressionHandler<FilterInvocation>
 		implements SecurityExpressionHandler<FilterInvocation> {
 		implements SecurityExpressionHandler<FilterInvocation> {
 
 
-	private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
+	private static final String DEFAULT_ROLE_PREFIX = "ROLE_";
 
 
-	private String defaultRolePrefix = "ROLE_";
+	private String defaultRolePrefix = DEFAULT_ROLE_PREFIX;
 
 
 	@Override
 	@Override
 	protected SecurityExpressionOperations createSecurityExpressionRoot(@Nullable Authentication authentication,
 	protected SecurityExpressionOperations createSecurityExpressionRoot(@Nullable Authentication authentication,
 			FilterInvocation fi) {
 			FilterInvocation fi) {
-		WebSecurityExpressionRoot root = new WebSecurityExpressionRoot(authentication, fi);
+		FilterInvocationExpressionRoot root = new FilterInvocationExpressionRoot(() -> authentication, fi);
+		root.setAuthorizationManagerFactory(getAuthorizationManagerFactory());
 		root.setPermissionEvaluator(getPermissionEvaluator());
 		root.setPermissionEvaluator(getPermissionEvaluator());
-		root.setTrustResolver(this.trustResolver);
-		root.setRoleHierarchy(getRoleHierarchy());
-		root.setDefaultRolePrefix(this.defaultRolePrefix);
+		if (!DEFAULT_ROLE_PREFIX.equals(this.defaultRolePrefix)) {
+			// Ensure SecurityExpressionRoot can strip the custom role prefix
+			root.setDefaultRolePrefix(this.defaultRolePrefix);
+		}
 		return root;
 		return root;
 	}
 	}
 
 
@@ -55,10 +58,12 @@ public class DefaultWebSecurityExpressionHandler extends AbstractSecurityExpress
 	 * {@link AuthenticationTrustResolverImpl}.
 	 * {@link AuthenticationTrustResolverImpl}.
 	 * @param trustResolver the {@link AuthenticationTrustResolver} to use. Cannot be
 	 * @param trustResolver the {@link AuthenticationTrustResolver} to use. Cannot be
 	 * null.
 	 * null.
+	 * @deprecated Use
+	 * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
 	 */
 	 */
+	@Deprecated(since = "7.0")
 	public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
 	public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
-		Assert.notNull(trustResolver, "trustResolver cannot be null");
-		this.trustResolver = trustResolver;
+		getDefaultAuthorizationManagerFactory().setTrustResolver(trustResolver);
 	}
 	}
 
 
 	/**
 	/**
@@ -75,8 +80,15 @@ public class DefaultWebSecurityExpressionHandler extends AbstractSecurityExpress
 	 * If null or empty, then no default role prefix is used.
 	 * If null or empty, then no default role prefix is used.
 	 * </p>
 	 * </p>
 	 * @param defaultRolePrefix the default prefix to add to roles. Default "ROLE_".
 	 * @param defaultRolePrefix the default prefix to add to roles. Default "ROLE_".
+	 * @deprecated Use
+	 * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
 	 */
 	 */
-	public void setDefaultRolePrefix(String defaultRolePrefix) {
+	@Deprecated(since = "7.0")
+	public void setDefaultRolePrefix(@Nullable String defaultRolePrefix) {
+		if (defaultRolePrefix == null) {
+			defaultRolePrefix = "";
+		}
+		getDefaultAuthorizationManagerFactory().setRolePrefix(defaultRolePrefix);
 		this.defaultRolePrefix = defaultRolePrefix;
 		this.defaultRolePrefix = defaultRolePrefix;
 	}
 	}
 
 

+ 62 - 0
web/src/main/java/org/springframework/security/web/access/expression/FilterInvocationExpressionRoot.java

@@ -0,0 +1,62 @@
+/*
+ * 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.web.access.expression;
+
+import java.util.function.Supplier;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+import org.springframework.security.access.expression.SecurityExpressionRoot;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.FilterInvocation;
+import org.springframework.security.web.util.matcher.IpAddressMatcher;
+
+/**
+ * @author Steve Riesenberg
+ * @since 7.0
+ */
+final class FilterInvocationExpressionRoot extends SecurityExpressionRoot<FilterInvocation> {
+
+	/**
+	 * Allows direct access to the request object
+	 */
+	public final HttpServletRequest request;
+
+	/**
+	 * Creates an instance for the given {@link Supplier} of the {@link Authentication}
+	 * and {@link HttpServletRequest}.
+	 * @param authentication the {@link Supplier} of the {@link Authentication} to use
+	 * @param fi the {@link FilterInvocation} to use
+	 */
+	FilterInvocationExpressionRoot(Supplier<Authentication> authentication, FilterInvocation fi) {
+		super(authentication, fi);
+		this.request = fi.getRequest();
+	}
+
+	/**
+	 * Takes a specific IP address or a range using the IP/Netmask (e.g. 192.168.1.0/24 or
+	 * 202.24.0.0/14).
+	 * @param ipAddress the address or range of addresses from which the request must
+	 * come.
+	 * @return true if the IP address of the current request is in the required range.
+	 */
+	public boolean hasIpAddress(String ipAddress) {
+		IpAddressMatcher matcher = new IpAddressMatcher(ipAddress);
+		return matcher.matches(this.request);
+	}
+
+}

+ 26 - 3
web/src/main/java/org/springframework/security/web/access/expression/WebSecurityExpressionRoot.java

@@ -24,22 +24,29 @@ import org.jspecify.annotations.Nullable;
 import org.springframework.security.access.expression.SecurityExpressionRoot;
 import org.springframework.security.access.expression.SecurityExpressionRoot;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.web.FilterInvocation;
 import org.springframework.security.web.FilterInvocation;
+import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
 import org.springframework.security.web.util.matcher.IpAddressMatcher;
 import org.springframework.security.web.util.matcher.IpAddressMatcher;
 
 
 /**
 /**
  * @author Luke Taylor
  * @author Luke Taylor
  * @author Evgeniy Cheban
  * @author Evgeniy Cheban
+ * @author Steve Riesenberg
  * @since 3.0
  * @since 3.0
  */
  */
-public class WebSecurityExpressionRoot extends SecurityExpressionRoot {
+public class WebSecurityExpressionRoot extends SecurityExpressionRoot<RequestAuthorizationContext> {
 
 
 	/**
 	/**
 	 * Allows direct access to the request object
 	 * Allows direct access to the request object
 	 */
 	 */
 	public final HttpServletRequest request;
 	public final HttpServletRequest request;
 
 
+	/**
+	 * @deprecated Use
+	 * {@link #WebSecurityExpressionRoot(Supplier, RequestAuthorizationContext)} instead
+	 */
+	@Deprecated(since = "7.0")
 	public WebSecurityExpressionRoot(@Nullable Authentication a, FilterInvocation fi) {
 	public WebSecurityExpressionRoot(@Nullable Authentication a, FilterInvocation fi) {
-		this(() -> a, fi.getRequest());
+		this(() -> a, new RequestAuthorizationContext(fi.getRequest()));
 	}
 	}
 
 
 	/**
 	/**
@@ -48,14 +55,30 @@ public class WebSecurityExpressionRoot extends SecurityExpressionRoot {
 	 * @param authentication the {@link Supplier} of the {@link Authentication} to use
 	 * @param authentication the {@link Supplier} of the {@link Authentication} to use
 	 * @param request the {@link HttpServletRequest} to use
 	 * @param request the {@link HttpServletRequest} to use
 	 * @since 5.8
 	 * @since 5.8
+	 * @deprecated Use
+	 * {@link #WebSecurityExpressionRoot(Supplier, RequestAuthorizationContext)} instead
 	 */
 	 */
+	@Deprecated(since = "7.0")
 	@SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1246
 	@SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1246
 	public WebSecurityExpressionRoot(Supplier<? extends @Nullable Authentication> authentication,
 	public WebSecurityExpressionRoot(Supplier<? extends @Nullable Authentication> authentication,
 			HttpServletRequest request) {
 			HttpServletRequest request) {
-		super(authentication);
+		super(authentication, new RequestAuthorizationContext(request));
 		this.request = request;
 		this.request = request;
 	}
 	}
 
 
+	/**
+	 * Creates an instance for the given {@link Supplier} of the {@link Authentication}
+	 * and {@link HttpServletRequest}.
+	 * @param authentication the {@link Supplier} of the {@link Authentication} to use
+	 * @param context the {@link RequestAuthorizationContext} to use
+	 * @since 7.0
+	 */
+	public WebSecurityExpressionRoot(Supplier<? extends @Nullable Authentication> authentication,
+			RequestAuthorizationContext context) {
+		super(authentication, context);
+		this.request = context.getRequest();
+	}
+
 	/**
 	/**
 	 * Takes a specific IP address or a range using the IP/Netmask (e.g. 192.168.1.0/24 or
 	 * Takes a specific IP address or a range using the IP/Netmask (e.g. 192.168.1.0/24 or
 	 * 202.24.0.0/14).
 	 * 202.24.0.0/14).