diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java
index 88e3eae23b..cee2c912ec 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java
@@ -37,8 +37,10 @@
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.aot.hint.PrePostAuthorizeHintsRegistrar;
import org.springframework.security.aot.hint.SecurityHintsRegistrar;
+import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authorization.AuthorizationEventPublisher;
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.AuthorizationManagerBeforeMethodInterceptor;
import org.springframework.security.authorization.method.MethodInvocationResult;
@@ -121,6 +123,16 @@ void setRoleHierarchy(RoleHierarchy roleHierarchy) {
this.expressionHandler.setRoleHierarchy(roleHierarchy);
}
+ @Autowired(required = false)
+ void setTrustResolver(AuthenticationTrustResolver trustResolver) {
+ this.expressionHandler.setTrustResolver(trustResolver);
+ }
+
+ @Autowired(required = false)
+ void setAuthorizationManagerFactory(AuthorizationManagerFactory authorizationManagerFactory) {
+ this.expressionHandler.setAuthorizationManagerFactory(authorizationManagerFactory);
+ }
+
@Autowired(required = false)
void setTemplateDefaults(AnnotationTemplateExpressionDefaults templateDefaults) {
this.preFilterMethodInterceptor.setTemplateDefaults(templateDefaults);
diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java
index 38858ecd95..c995f9427f 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java
@@ -18,7 +18,6 @@
import java.util.List;
import java.util.function.Function;
-import java.util.function.Supplier;
import jakarta.servlet.http.HttpServletRequest;
@@ -27,13 +26,12 @@
import org.springframework.core.ResolvableType;
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
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.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
+import org.springframework.security.authorization.AuthorizationManagerFactory;
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.config.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
@@ -46,7 +44,6 @@
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcherEntry;
import org.springframework.util.Assert;
-import org.springframework.util.function.SingletonSupplier;
/**
* Adds a URL based authorization using {@link AuthorizationManager}.
@@ -62,9 +59,7 @@ public final class AuthorizeHttpRequestsConfigurer roleHierarchy;
-
- private String rolePrefix = "ROLE_";
+ private final AuthorizationManagerFactory authorizationManagerFactory;
private ObjectPostProcessor> postProcessor = ObjectPostProcessor
.identity();
@@ -81,13 +76,7 @@ public AuthorizeHttpRequestsConfigurer(ApplicationContext context) {
else {
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.forClassWithGenerics(AuthorizationManager.class, HttpServletRequest.class));
ObjectProvider>> provider = context
@@ -95,6 +84,28 @@ public AuthorizeHttpRequestsConfigurer(ApplicationContext context) {
provider.ifUnique((postProcessor) -> this.postProcessor = postProcessor);
}
+ private AuthorizationManagerFactory getAuthorizationManagerFactory(
+ ApplicationContext context) {
+ ResolvableType authorizationManagerFactoryType = ResolvableType
+ .forClassWithGenerics(AuthorizationManagerFactory.class, RequestAuthorizationContext.class);
+ ObjectProvider> 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 authorizationManagerFactory = new DefaultAuthorizationManagerFactory<>();
+ authorizationManagerFactory.setRoleHierarchy(roleHierarchy);
+ authorizationManagerFactory.setRolePrefix(rolePrefix);
+
+ return authorizationManagerFactory;
+ });
+ }
+
/**
* The {@link AuthorizationManagerRequestMatcherRegistry} is what users will interact
* with after applying the {@link AuthorizeHttpRequestsConfigurer}.
@@ -173,7 +184,7 @@ private AuthorizationManager createAuthorizationManager() {
@Override
protected AuthorizedUrl chainRequestMatchers(List requestMatchers) {
this.unmappedMatchers = requestMatchers;
- return new AuthorizedUrl(requestMatchers);
+ return new AuthorizedUrl(requestMatchers, AuthorizeHttpRequestsConfigurer.this.authorizationManagerFactory);
}
/**
@@ -201,20 +212,31 @@ public class AuthorizedUrl {
private final List extends RequestMatcher> matchers;
+ private AuthorizationManagerFactory authorizationManagerFactory;
+
private boolean not;
/**
* Creates an instance.
* @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 authorizationManagerFactory) {
this.matchers = matchers;
+ this.authorizationManagerFactory = authorizationManagerFactory;
}
protected List extends RequestMatcher> getMatchers() {
return this.matchers;
}
+ void setAuthorizationManagerFactory(
+ AuthorizationManagerFactory authorizationManagerFactory) {
+ this.authorizationManagerFactory = authorizationManagerFactory;
+ }
+
/**
* Negates the following authorization rule.
* @return the {@link AuthorizedUrl} for further customization
@@ -231,7 +253,7 @@ public AuthorizedUrl not() {
* customizations
*/
public AuthorizationManagerRequestMatcherRegistry permitAll() {
- return access(SingleResultAuthorizationManager.permitAll());
+ return access(this.authorizationManagerFactory.permitAll());
}
/**
@@ -240,7 +262,7 @@ public AuthorizationManagerRequestMatcherRegistry permitAll() {
* customizations
*/
public AuthorizationManagerRequestMatcherRegistry denyAll() {
- return access(SingleResultAuthorizationManager.denyAll());
+ return access(this.authorizationManagerFactory.denyAll());
}
/**
@@ -251,8 +273,7 @@ public AuthorizationManagerRequestMatcherRegistry denyAll() {
* customizations
*/
public AuthorizationManagerRequestMatcherRegistry hasRole(String role) {
- return access(withRoleHierarchy(AuthorityAuthorizationManager
- .hasAnyRole(AuthorizeHttpRequestsConfigurer.this.rolePrefix, new String[] { role })));
+ return access(this.authorizationManagerFactory.hasRole(role));
}
/**
@@ -264,8 +285,7 @@ public AuthorizationManagerRequestMatcherRegistry hasRole(String role) {
* customizations
*/
public AuthorizationManagerRequestMatcherRegistry hasAnyRole(String... roles) {
- return access(withRoleHierarchy(
- AuthorityAuthorizationManager.hasAnyRole(AuthorizeHttpRequestsConfigurer.this.rolePrefix, roles)));
+ return access(this.authorizationManagerFactory.hasAnyRole(roles));
}
/**
@@ -275,7 +295,7 @@ public AuthorizationManagerRequestMatcherRegistry hasAnyRole(String... roles) {
* customizations
*/
public AuthorizationManagerRequestMatcherRegistry hasAuthority(String authority) {
- return access(withRoleHierarchy(AuthorityAuthorizationManager.hasAuthority(authority)));
+ return access(this.authorizationManagerFactory.hasAuthority(authority));
}
/**
@@ -286,13 +306,7 @@ public AuthorizationManagerRequestMatcherRegistry hasAuthority(String authority)
* customizations
*/
public AuthorizationManagerRequestMatcherRegistry hasAnyAuthority(String... authorities) {
- return access(withRoleHierarchy(AuthorityAuthorizationManager.hasAnyAuthority(authorities)));
- }
-
- private AuthorityAuthorizationManager withRoleHierarchy(
- AuthorityAuthorizationManager manager) {
- manager.setRoleHierarchy(AuthorizeHttpRequestsConfigurer.this.roleHierarchy.get());
- return manager;
+ return access(this.authorizationManagerFactory.hasAnyAuthority(authorities));
}
/**
@@ -301,7 +315,7 @@ private AuthorityAuthorizationManager withRoleHiera
* customizations
*/
public AuthorizationManagerRequestMatcherRegistry authenticated() {
- return access(AuthenticatedAuthorizationManager.authenticated());
+ return access(this.authorizationManagerFactory.authenticated());
}
/**
@@ -313,7 +327,7 @@ public AuthorizationManagerRequestMatcherRegistry authenticated() {
* @see RememberMeConfigurer
*/
public AuthorizationManagerRequestMatcherRegistry fullyAuthenticated() {
- return access(AuthenticatedAuthorizationManager.fullyAuthenticated());
+ return access(this.authorizationManagerFactory.fullyAuthenticated());
}
/**
@@ -324,7 +338,7 @@ public AuthorizationManagerRequestMatcherRegistry fullyAuthenticated() {
* @see RememberMeConfigurer
*/
public AuthorizationManagerRequestMatcherRegistry rememberMe() {
- return access(AuthenticatedAuthorizationManager.rememberMe());
+ return access(this.authorizationManagerFactory.rememberMe());
}
/**
@@ -334,7 +348,7 @@ public AuthorizationManagerRequestMatcherRegistry rememberMe() {
* @since 5.8
*/
public AuthorizationManagerRequestMatcherRegistry anonymous() {
- return access(AuthenticatedAuthorizationManager.anonymous());
+ return access(this.authorizationManagerFactory.anonymous());
}
/**
diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java
index 16a9831393..6e132a15fb 100644
--- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java
+++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java
@@ -16,13 +16,15 @@
package org.springframework.security.config.annotation.web.configurers;
+import java.util.Set;
import java.util.function.Supplier;
+import jakarta.servlet.http.HttpServletRequest;
+
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.ObservationTextPublisher;
-import jakarta.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
@@ -36,14 +38,19 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
+import org.springframework.http.HttpMethod;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.authentication.RememberMeAuthenticationToken;
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.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
+import org.springframework.security.authorization.AuthorizationManagerFactory;
import org.springframework.security.authorization.AuthorizationObservationContext;
+import org.springframework.security.authorization.SingleResultAuthorizationManager;
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
import org.springframework.security.authorization.event.AuthorizationDeniedEvent;
import org.springframework.security.config.ObjectPostProcessor;
@@ -81,13 +88,17 @@
import static org.assertj.core.api.Assertions.assertThat;
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.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
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.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.csrf;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
@@ -169,6 +180,26 @@ public void configureMvcMatcherAccessAuthorizationManagerWhenNullThenException()
.withMessageContaining("manager cannot be null");
}
+ @Test
+ public void configureWhenCustomAuthorizationManagerFactoryRegisteredThenUsed() {
+ AuthorizationManager authorizationManager = mock();
+ AuthorizationManagerFactory 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
public void configureWhenObjectPostProcessorRegisteredThenInvokedOnAuthorizationManagerAndAuthorizationFilter() {
this.spring.register(ObjectPostProcessorConfig.class).autowire();
@@ -537,6 +568,205 @@ public void getWhenCustomRolePrefixAndHasAnyRoleThenRespondsWithOk() throws Exce
this.mvc.perform(requestWithAdmin).andExpect(status().isOk());
}
+ @Test
+ public void getWhenCustomAuthorizationManagerFactoryRegisteredAndPermitAllThenRespondsWithOk() throws Exception {
+ AuthorizationManager authorizationManager = mock();
+ AuthorizationManagerFactory authorizationManagerFactory = mockAuthorizationManagerFactory(
+ authorizationManager);
+ AuthorizationManager 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 authorizationManager = mock();
+ AuthorizationManagerFactory authorizationManagerFactory = mockAuthorizationManagerFactory(
+ authorizationManager);
+ AuthorizationManager 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 authorizationManager = mock();
+ AuthorizationManagerFactory authorizationManagerFactory = mockAuthorizationManagerFactory(
+ authorizationManager);
+ AuthorizationManager 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 authorizationManager = mock();
+ AuthorizationManagerFactory authorizationManagerFactory = mockAuthorizationManagerFactory(
+ authorizationManager);
+ AuthorizationManager 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 authorizationManager = mock();
+ AuthorizationManagerFactory authorizationManagerFactory = mockAuthorizationManagerFactory(
+ authorizationManager);
+ AuthorizationManager 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 authorizationManager = mock();
+ AuthorizationManagerFactory authorizationManagerFactory = mockAuthorizationManagerFactory(
+ authorizationManager);
+ AuthorizationManager 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 authorizationManager = mock();
+ AuthorizationManagerFactory authorizationManagerFactory = mockAuthorizationManagerFactory(
+ authorizationManager);
+ AuthorizationManager 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 authorizationManager = mock();
+ AuthorizationManagerFactory authorizationManagerFactory = mockAuthorizationManagerFactory(
+ authorizationManager);
+ AuthorizationManager 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 authorizationManager = mock();
+ AuthorizationManagerFactory authorizationManagerFactory = mockAuthorizationManagerFactory(
+ authorizationManager);
+ AuthorizationManager 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 authorizationManager = mock();
+ AuthorizationManagerFactory authorizationManagerFactory = mockAuthorizationManagerFactory(
+ authorizationManager);
+ AuthorizationManager 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 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
public void getWhenExpressionHasIpAddressLocalhostConfiguredIpAddressIsLocalhostThenRespondsWithOk()
throws Exception {
@@ -586,6 +816,23 @@ private static RequestPostProcessor remoteAddress(String remoteAddress) {
};
}
+ private AuthorizationManagerFactory mockAuthorizationManagerFactory(
+ AuthorizationManager authorizationManager) {
+ AuthorizationManagerFactory 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
public void getWhenFullyAuthenticatedConfiguredAndRememberMeTokenThenRespondsWithUnauthorized() throws Exception {
this.spring.register(FullyAuthenticatedConfig.class, BasicController.class).autowire();
@@ -849,6 +1096,41 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
}
+ @Configuration
+ @EnableWebSecurity
+ static class AuthorizationManagerFactoryConfig {
+
+ static AuthorizationManagerFactory authorizationManagerFactory;
+
+ @Bean
+ AuthorizationManagerFactory 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
@EnableWebSecurity
static class ObjectPostProcessorConfig {
@@ -1287,6 +1569,47 @@ void path() {
}
+ @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
static class ObservationRegistryConfig {
diff --git a/core/src/main/java/org/springframework/security/access/expression/AbstractSecurityExpressionHandler.java b/core/src/main/java/org/springframework/security/access/expression/AbstractSecurityExpressionHandler.java
index e710d9442c..9d3bb17dcb 100644
--- a/core/src/main/java/org/springframework/security/access/expression/AbstractSecurityExpressionHandler.java
+++ b/core/src/main/java/org/springframework/security/access/expression/AbstractSecurityExpressionHandler.java
@@ -28,6 +28,8 @@
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.security.access.PermissionEvaluator;
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.util.Assert;
@@ -47,7 +49,9 @@ public abstract class AbstractSecurityExpressionHandler
private @Nullable BeanResolver beanResolver;
- private @Nullable RoleHierarchy roleHierarchy;
+ private final DefaultAuthorizationManagerFactory defaultAuthorizationManagerFactory = new DefaultAuthorizationManagerFactory<>();
+
+ private AuthorizationManagerFactory authorizationManagerFactory = this.defaultAuthorizationManagerFactory;
private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator();
@@ -105,12 +109,43 @@ protected StandardEvaluationContext createEvaluationContextInternal(Authenticati
protected abstract SecurityExpressionOperations createSecurityExpressionRoot(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 authorizationManagerFactory) {
+ Assert.notNull(authorizationManagerFactory, "authorizationManagerFactory cannot be null");
+ this.authorizationManagerFactory = authorizationManagerFactory;
+ }
+
+ protected final AuthorizationManagerFactory getAuthorizationManagerFactory() {
+ return this.authorizationManagerFactory;
+ }
+
+ protected final DefaultAuthorizationManagerFactory getDefaultAuthorizationManagerFactory() {
+ return this.defaultAuthorizationManagerFactory;
+ }
+
+ /**
+ * @deprecated Use {@link #getDefaultAuthorizationManagerFactory()} instead
+ */
+ @Deprecated(since = "7.0")
protected @Nullable RoleHierarchy getRoleHierarchy() {
- return this.roleHierarchy;
+ return this.defaultAuthorizationManagerFactory.getRoleHierarchy();
}
- public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
- this.roleHierarchy = roleHierarchy;
+ /**
+ * @deprecated Use
+ * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
+ */
+ @Deprecated(since = "7.0")
+ public void setRoleHierarchy(@Nullable RoleHierarchy roleHierarchy) {
+ if (roleHierarchy != null) {
+ this.defaultAuthorizationManagerFactory.setRoleHierarchy(roleHierarchy);
+ }
}
protected PermissionEvaluator getPermissionEvaluator() {
diff --git a/core/src/main/java/org/springframework/security/access/expression/SecurityExpressionRoot.java b/core/src/main/java/org/springframework/security/access/expression/SecurityExpressionRoot.java
index c16faece85..7dd16efd71 100644
--- a/core/src/main/java/org/springframework/security/access/expression/SecurityExpressionRoot.java
+++ b/core/src/main/java/org/springframework/security/access/expression/SecurityExpressionRoot.java
@@ -17,8 +17,6 @@
package org.springframework.security.access.expression;
import java.io.Serializable;
-import java.util.Collection;
-import java.util.Set;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
@@ -26,10 +24,11 @@
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
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.GrantedAuthority;
-import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.util.Assert;
import org.springframework.util.function.SingletonSupplier;
@@ -38,19 +37,20 @@
*
* @author Luke Taylor
* @author Evgeniy Cheban
+ * @author Steve Riesenberg
* @since 3.0
*/
-public abstract class SecurityExpressionRoot implements SecurityExpressionOperations {
+public abstract class SecurityExpressionRoot implements SecurityExpressionOperations {
- private final Supplier authentication;
+ private static final AuthorizationManagerFactory> DEFAULT_AUTHORIZATION_MANAGER_FACTORY = new DefaultAuthorizationManagerFactory<>();
- private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
+ private final Supplier authentication;
- private @Nullable RoleHierarchy roleHierarchy;
+ private final @Nullable T object;
- private @Nullable Set roles;
+ private @Nullable DefaultAuthorizationManagerFactory defaultAuthorizationManagerFactory;
- private String defaultRolePrefix = "ROLE_";
+ private AuthorizationManagerFactory authorizationManagerFactory = defaultAuthorizationManagerFactory();
/**
* Allows "permitAll" expression
@@ -77,9 +77,11 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
/**
* Creates a new instance
* @param authentication the {@link Authentication} to use. Cannot be null.
+ * @deprecated use {@link #SecurityExpressionRoot(Supplier, Object)} instead
*/
+ @Deprecated(since = "7.0")
public SecurityExpressionRoot(Authentication authentication) {
- this(() -> authentication);
+ this(() -> authentication, null);
}
/**
@@ -88,44 +90,48 @@ public SecurityExpressionRoot(Authentication authentication) {
* @param authentication the {@link Supplier} of the {@link Authentication} to use.
* Cannot be null.
* @since 5.8
+ * @deprecated use {@link #SecurityExpressionRoot(Supplier, Object)} instead
*/
+ @Deprecated(since = "7.0")
public SecurityExpressionRoot(Supplier 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 authentication, @Nullable T object) {
this.authentication = SingletonSupplier.of(() -> {
Authentication value = authentication.get();
Assert.notNull(value, "Authentication object cannot be null");
return value;
});
+ this.object = object;
}
@Override
public final boolean hasAuthority(String authority) {
- return hasAnyAuthority(authority);
+ return isGranted(this.authorizationManagerFactory.hasAnyAuthority(authority));
}
@Override
public final boolean hasAnyAuthority(String... authorities) {
- return hasAnyAuthorityName(null, authorities);
+ return isGranted(this.authorizationManagerFactory.hasAnyAuthority(authorities));
}
@Override
public final boolean hasRole(String role) {
- return hasAnyRole(role);
+ return isGranted(this.authorizationManagerFactory.hasRole(role));
}
@Override
public final boolean hasAnyRole(String... roles) {
- return hasAnyAuthorityName(this.defaultRolePrefix, roles);
- }
-
- private boolean hasAnyAuthorityName(@Nullable String prefix, String... roles) {
- Set roleSet = getAuthoritySet();
- for (String role : roles) {
- String defaultedRole = getRoleWithDefaultPrefix(prefix, role);
- if (roleSet.contains(defaultedRole)) {
- return true;
- }
- }
- return false;
+ return isGranted(this.authorizationManagerFactory.hasAnyRole(roles));
}
@Override
@@ -135,33 +141,38 @@ public final Authentication getAuthentication() {
@Override
public final boolean permitAll() {
- return true;
+ return isGranted(this.authorizationManagerFactory.permitAll());
}
@Override
public final boolean denyAll() {
- return false;
+ return isGranted(this.authorizationManagerFactory.denyAll());
}
@Override
public final boolean isAnonymous() {
- return this.trustResolver.isAnonymous(getAuthentication());
+ return isGranted(this.authorizationManagerFactory.anonymous());
}
@Override
public final boolean isAuthenticated() {
- return this.trustResolver.isAuthenticated(getAuthentication());
+ return isGranted(this.authorizationManagerFactory.authenticated());
}
@Override
public final boolean isRememberMe() {
- return this.trustResolver.isRememberMe(getAuthentication());
+ return isGranted(this.authorizationManagerFactory.rememberMe());
}
@Override
public final boolean isFullyAuthenticated() {
- Authentication authentication = getAuthentication();
- return this.trustResolver.isFullyAuthenticated(authentication);
+ return isGranted(this.authorizationManagerFactory.fullyAuthenticated());
+ }
+
+ @SuppressWarnings({ "DataFlowIssue", "NullAway" })
+ private boolean isGranted(AuthorizationManager authorizationManager) {
+ AuthorizationResult authorizationResult = authorizationManager.authorize(this.authentication, this.object);
+ return (authorizationResult != null && authorizationResult.isGranted());
}
/**
@@ -173,12 +184,22 @@ public final boolean isFullyAuthenticated() {
return getAuthentication().getPrincipal();
}
+ /**
+ * @deprecated Use
+ * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
+ */
+ @Deprecated(since = "7.0")
public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
- this.trustResolver = trustResolver;
+ getDefaultAuthorizationManagerFactory().setTrustResolver(trustResolver);
}
+ /**
+ * @deprecated Use
+ * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
+ */
+ @Deprecated(since = "7.0")
public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
- this.roleHierarchy = roleHierarchy;
+ getDefaultAuthorizationManagerFactory().setRoleHierarchy(roleHierarchy);
}
/**
@@ -193,20 +214,32 @@ public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
* If null or empty, then no default role prefix is used.
*
* @param defaultRolePrefix the default prefix to add to roles. Default "ROLE_".
+ * @deprecated Use
+ * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
*/
+ @Deprecated(since = "7.0")
public void setDefaultRolePrefix(String defaultRolePrefix) {
- this.defaultRolePrefix = defaultRolePrefix;
+ getDefaultAuthorizationManagerFactory().setRolePrefix(defaultRolePrefix);
+ }
+
+ /**
+ * 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 authorizationManagerFactory) {
+ Assert.notNull(authorizationManagerFactory, "authorizationManagerFactory cannot be null");
+ this.authorizationManagerFactory = authorizationManagerFactory;
}
- private Set 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);
+ private DefaultAuthorizationManagerFactory getDefaultAuthorizationManagerFactory() {
+ if (this.defaultAuthorizationManagerFactory == null) {
+ this.defaultAuthorizationManagerFactory = new DefaultAuthorizationManagerFactory<>();
+ this.authorizationManagerFactory = this.defaultAuthorizationManagerFactory;
}
- return this.roles;
+
+ return this.defaultAuthorizationManagerFactory;
}
@Override
@@ -225,24 +258,9 @@ public void setPermissionEvaluator(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;
+ @SuppressWarnings("unchecked")
+ private static AuthorizationManagerFactory defaultAuthorizationManagerFactory() {
+ return (AuthorizationManagerFactory) DEFAULT_AUTHORIZATION_MANAGER_FACTORY;
}
}
diff --git a/core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java b/core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java
index 4450aae48d..9292958fa1 100644
--- a/core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java
+++ b/core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java
@@ -43,6 +43,8 @@
import org.springframework.security.access.expression.ExpressionUtils;
import org.springframework.security.authentication.AuthenticationTrustResolver;
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.parameters.DefaultSecurityParameterNameDiscoverer;
import org.springframework.util.Assert;
@@ -56,6 +58,7 @@
* @author Luke Taylor
* @author Evgeniy Cheban
* @author Blagoja Stamatovski
+ * @author Steve Riesenberg
* @since 3.0
*/
public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpressionHandler
@@ -63,14 +66,10 @@ public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpr
protected final Log logger = LogFactory.getLog(getClass());
- private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
-
private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultSecurityParameterNameDiscoverer();
private @Nullable PermissionCacheOptimizer permissionCacheOptimizer = null;
- private String defaultRolePrefix = "ROLE_";
-
public DefaultMethodSecurityExpressionHandler() {
}
@@ -103,12 +102,10 @@ protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authen
private MethodSecurityExpressionOperations createSecurityExpressionRoot(Supplier authentication,
MethodInvocation invocation) {
- MethodSecurityExpressionRoot root = new MethodSecurityExpressionRoot(authentication);
- root.setThis(invocation.getThis());
+ MethodSecurityExpressionRoot root = new MethodSecurityExpressionRoot(authentication, invocation);
+ root.setAuthorizationManagerFactory(getAuthorizationManagerFactory());
root.setPermissionEvaluator(getPermissionEvaluator());
- root.setTrustResolver(getTrustResolver());
- Optional.ofNullable(getRoleHierarchy()).ifPresent(root::setRoleHierarchy);
- root.setDefaultRolePrefix(getDefaultRolePrefix());
+ root.setThis(invocation.getThis());
return root;
}
@@ -229,17 +226,23 @@ private Object filterStream(final Stream> filterTarget, Expression filterExpre
* {@link AuthenticationTrustResolverImpl}.
* @param trustResolver the {@link AuthenticationTrustResolver} to use. Cannot be
* null.
+ * @deprecated use
+ * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
*/
+ @Deprecated(since = "7.0")
public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
Assert.notNull(trustResolver, "trustResolver cannot be null");
- this.trustResolver = trustResolver;
+ getDefaultAuthorizationManagerFactory().setTrustResolver(trustResolver);
}
/**
* @return The current {@link AuthenticationTrustResolver}
+ * @deprecated use {@link DefaultAuthorizationManagerFactory#getTrustResolver()}
+ * instead
*/
+ @Deprecated(since = "7.0")
protected AuthenticationTrustResolver getTrustResolver() {
- return this.trustResolver;
+ return getDefaultAuthorizationManagerFactory().getTrustResolver();
}
/**
@@ -286,16 +289,21 @@ public void setReturnObject(@Nullable Object returnObject, EvaluationContext ctx
* If null or empty, then no default role prefix is used.
*
* @param defaultRolePrefix the default prefix to add to roles. Default "ROLE_".
+ * @deprecated use
+ * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
*/
+ @Deprecated(since = "7.0")
public void setDefaultRolePrefix(String defaultRolePrefix) {
- this.defaultRolePrefix = defaultRolePrefix;
+ getDefaultAuthorizationManagerFactory().setRolePrefix(defaultRolePrefix);
}
/**
* @return The default role prefix
+ * @deprecated use {@link DefaultAuthorizationManagerFactory#getRolePrefix()} instead
*/
+ @Deprecated(since = "7.0")
protected String getDefaultRolePrefix() {
- return this.defaultRolePrefix;
+ return getDefaultAuthorizationManagerFactory().getRolePrefix();
}
}
diff --git a/core/src/main/java/org/springframework/security/access/expression/method/MethodSecurityExpressionRoot.java b/core/src/main/java/org/springframework/security/access/expression/method/MethodSecurityExpressionRoot.java
index 1aba5ba84f..2fc50a86a6 100644
--- a/core/src/main/java/org/springframework/security/access/expression/method/MethodSecurityExpressionRoot.java
+++ b/core/src/main/java/org/springframework/security/access/expression/method/MethodSecurityExpressionRoot.java
@@ -18,6 +18,7 @@
import java.util.function.Supplier;
+import org.aopalliance.intercept.MethodInvocation;
import org.jspecify.annotations.Nullable;
import org.springframework.security.access.expression.SecurityExpressionRoot;
@@ -28,9 +29,11 @@
*
* @author Luke Taylor
* @author Evgeniy Cheban
+ * @author Steve Riesenberg
* @since 3.0
*/
-class MethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
+final class MethodSecurityExpressionRoot extends SecurityExpressionRoot
+ implements MethodSecurityExpressionOperations {
private @Nullable Object filterObject;
@@ -38,12 +41,8 @@ class MethodSecurityExpressionRoot extends SecurityExpressionRoot implements Met
private @Nullable Object target;
- MethodSecurityExpressionRoot(Authentication a) {
- super(a);
- }
-
- MethodSecurityExpressionRoot(Supplier authentication) {
- super(authentication);
+ MethodSecurityExpressionRoot(Supplier authentication, MethodInvocation methodInvocation) {
+ super(authentication, methodInvocation);
}
@Override
diff --git a/core/src/main/java/org/springframework/security/authorization/AuthorizationManagerFactory.java b/core/src/main/java/org/springframework/security/authorization/AuthorizationManagerFactory.java
new file mode 100644
index 0000000000..8840ab21ed
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/authorization/AuthorizationManagerFactory.java
@@ -0,0 +1,122 @@
+/*
+ * 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;
+
+/**
+ * A factory for creating different kinds of {@link AuthorizationManager} instances.
+ *
+ * @param the type of object that the authorization check is being done on
+ * @author Steve Riesenberg
+ * @since 7.0
+ */
+public interface AuthorizationManagerFactory {
+
+ /**
+ * Create an {@link AuthorizationManager} that allows anyone.
+ * @return A new {@link AuthorizationManager} instance
+ */
+ default AuthorizationManager permitAll() {
+ return SingleResultAuthorizationManager.permitAll();
+ }
+
+ /**
+ * Creates an {@link AuthorizationManager} that does not allow anyone.
+ * @return A new {@link AuthorizationManager} instance
+ */
+ default AuthorizationManager 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 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 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. USER,
+ * ADMIN, etc.)
+ * @return A new {@link AuthorizationManager} instance
+ */
+ default AuthorizationManager 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 hasAnyAuthority(String... authorities) {
+ return AuthorityAuthorizationManager.hasAnyAuthority(authorities);
+ }
+
+ /**
+ * Creates an {@link AuthorizationManager} that allows any authenticated user.
+ * @return A new {@link AuthorizationManager} instance
+ */
+ default AuthorizationManager 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 fullyAuthenticated() {
+ return AuthenticatedAuthorizationManager.fullyAuthenticated();
+ }
+
+ /**
+ * Creates an {@link AuthorizationManager} that allows users that have been
+ * remembered.
+ * @return A new {@link AuthorizationManager} instance
+ */
+ default AuthorizationManager rememberMe() {
+ return AuthenticatedAuthorizationManager.rememberMe();
+ }
+
+ /**
+ * Creates an {@link AuthorizationManager} that allows only anonymous users.
+ * @return A new {@link AuthorizationManager} instance
+ */
+ default AuthorizationManager anonymous() {
+ return AuthenticatedAuthorizationManager.anonymous();
+ }
+
+}
diff --git a/core/src/main/java/org/springframework/security/authorization/DefaultAuthorizationManagerFactory.java b/core/src/main/java/org/springframework/security/authorization/DefaultAuthorizationManagerFactory.java
new file mode 100644
index 0000000000..c05bc3e6bd
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/authorization/DefaultAuthorizationManagerFactory.java
@@ -0,0 +1,145 @@
+/*
+ * 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.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 the type of object that the authorization check is being done on
+ * @author Steve Riesenberg
+ * @since 7.0
+ */
+public final class DefaultAuthorizationManagerFactory implements AuthorizationManagerFactory {
+
+ private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
+
+ private RoleHierarchy roleHierarchy = new NullRoleHierarchy();
+
+ private String rolePrefix = "ROLE_";
+
+ /**
+ * Returns the {@link AuthenticationTrustResolver} used to check the user's
+ * authentication.
+ * @return the {@link AuthenticationTrustResolver}
+ */
+ public AuthenticationTrustResolver getTrustResolver() {
+ return this.trustResolver;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Returns the {@link RoleHierarchy} used to discover reachable authorities.
+ * @return the {@link RoleHierarchy}
+ */
+ public RoleHierarchy getRoleHierarchy() {
+ return this.roleHierarchy;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Returns the prefix used to create an authority name from a role name.
+ * @return the role prefix
+ */
+ public String getRolePrefix() {
+ return this.rolePrefix;
+ }
+
+ /**
+ * 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 hasRole(String role) {
+ return hasAnyRole(role);
+ }
+
+ @Override
+ public AuthorizationManager hasAnyRole(String... roles) {
+ return withRoleHierarchy(AuthorityAuthorizationManager.hasAnyRole(this.rolePrefix, roles));
+ }
+
+ @Override
+ public AuthorizationManager hasAuthority(String authority) {
+ return withRoleHierarchy(AuthorityAuthorizationManager.hasAuthority(authority));
+ }
+
+ @Override
+ public AuthorizationManager hasAnyAuthority(String... authorities) {
+ return withRoleHierarchy(AuthorityAuthorizationManager.hasAnyAuthority(authorities));
+ }
+
+ @Override
+ public AuthorizationManager authenticated() {
+ return withTrustResolver(AuthenticatedAuthorizationManager.authenticated());
+ }
+
+ @Override
+ public AuthorizationManager fullyAuthenticated() {
+ return withTrustResolver(AuthenticatedAuthorizationManager.fullyAuthenticated());
+ }
+
+ @Override
+ public AuthorizationManager rememberMe() {
+ return withTrustResolver(AuthenticatedAuthorizationManager.rememberMe());
+ }
+
+ @Override
+ public AuthorizationManager anonymous() {
+ return withTrustResolver(AuthenticatedAuthorizationManager.anonymous());
+ }
+
+ private AuthorityAuthorizationManager withRoleHierarchy(AuthorityAuthorizationManager authorizationManager) {
+ authorizationManager.setRoleHierarchy(this.roleHierarchy);
+ return authorizationManager;
+ }
+
+ private AuthenticatedAuthorizationManager withTrustResolver(
+ AuthenticatedAuthorizationManager authorizationManager) {
+ authorizationManager.setTrustResolver(this.trustResolver);
+ return authorizationManager;
+ }
+
+}
diff --git a/core/src/test/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandlerTests.java b/core/src/test/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandlerTests.java
index e91ccb36db..e3d8f3450f 100644
--- a/core/src/test/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandlerTests.java
+++ b/core/src/test/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandlerTests.java
@@ -35,7 +35,6 @@
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.TypedValue;
-import org.springframework.security.access.expression.SecurityExpressionRoot;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -182,7 +181,7 @@ public void createEvaluationContextSupplierAuthentication() {
verifyNoInteractions(mockAuthenticationSupplier);
assertThat(context.getRootObject()).extracting(TypedValue::getValue)
.asInstanceOf(InstanceOfAssertFactories.type(MethodSecurityExpressionRoot.class))
- .extracting(SecurityExpressionRoot::getAuthentication)
+ .extracting(MethodSecurityExpressionRoot::getAuthentication)
.isEqualTo(this.authentication);
verify(mockAuthenticationSupplier).get();
}
diff --git a/core/src/test/java/org/springframework/security/access/expression/method/MethodSecurityExpressionRootTests.java b/core/src/test/java/org/springframework/security/access/expression/method/MethodSecurityExpressionRootTests.java
index 5df9dc7b0a..f8c9e83f9a 100644
--- a/core/src/test/java/org/springframework/security/access/expression/method/MethodSecurityExpressionRootTests.java
+++ b/core/src/test/java/org/springframework/security/access/expression/method/MethodSecurityExpressionRootTests.java
@@ -16,6 +16,7 @@
package org.springframework.security.access.expression.method;
+import org.aopalliance.intercept.MethodInvocation;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -25,6 +26,7 @@
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.ExpressionUtils;
import org.springframework.security.authentication.AuthenticationTrustResolver;
+import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
import org.springframework.security.core.Authentication;
import static org.assertj.core.api.Assertions.assertThat;
@@ -53,11 +55,13 @@ public class MethodSecurityExpressionRootTests {
@BeforeEach
public void createContext() {
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.setRootObject(this.root);
this.trustResolver = mock(AuthenticationTrustResolver.class);
- this.root.setTrustResolver(this.trustResolver);
+ DefaultAuthorizationManagerFactory authorizationManagerFactory = new DefaultAuthorizationManagerFactory();
+ authorizationManagerFactory.setTrustResolver(this.trustResolver);
+ this.root.setAuthorizationManagerFactory(authorizationManagerFactory);
}
@Test
diff --git a/core/src/test/java/org/springframework/security/authorization/AuthorizationManagerFactoryTests.java b/core/src/test/java/org/springframework/security/authorization/AuthorizationManagerFactoryTests.java
new file mode 100644
index 0000000000..8ea9ed24f5
--- /dev/null
+++ b/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 factory = new DefaultAuthorizationManagerFactory<>();
+ AuthorizationManager authorizationManager = factory.permitAll();
+ assertThat(authorizationManager).isInstanceOf(SingleResultAuthorizationManager.class);
+ }
+
+ @Test
+ public void denyAllReturnsSingleResultAuthorizationManagerByDefault() {
+ AuthorizationManagerFactory factory = new DefaultAuthorizationManagerFactory<>();
+ AuthorizationManager authorizationManager = factory.denyAll();
+ assertThat(authorizationManager).isInstanceOf(SingleResultAuthorizationManager.class);
+ }
+
+ @Test
+ public void hasRoleReturnsAuthorityAuthorizationManagerByDefault() {
+ AuthorizationManagerFactory factory = new DefaultAuthorizationManagerFactory<>();
+ AuthorizationManager authorizationManager = factory.hasRole("USER");
+ assertThat(authorizationManager).isInstanceOf(AuthorityAuthorizationManager.class);
+ }
+
+ @Test
+ public void hasAnyRoleReturnsAuthorityAuthorizationManagerByDefault() {
+ AuthorizationManagerFactory factory = new DefaultAuthorizationManagerFactory<>();
+ AuthorizationManager authorizationManager = factory.hasAnyRole("USER", "ADMIN");
+ assertThat(authorizationManager).isInstanceOf(AuthorityAuthorizationManager.class);
+ }
+
+ @Test
+ public void hasAuthorityReturnsAuthorityAuthorizationManagerByDefault() {
+ AuthorizationManagerFactory factory = new DefaultAuthorizationManagerFactory<>();
+ AuthorizationManager authorizationManager = factory.hasAuthority("authority1");
+ assertThat(authorizationManager).isInstanceOf(AuthorityAuthorizationManager.class);
+ }
+
+ @Test
+ public void hasAnyAuthorityReturnsAuthorityAuthorizationManagerByDefault() {
+ AuthorizationManagerFactory factory = new DefaultAuthorizationManagerFactory<>();
+ AuthorizationManager authorizationManager = factory.hasAnyAuthority("authority1", "authority2");
+ assertThat(authorizationManager).isInstanceOf(AuthorityAuthorizationManager.class);
+ }
+
+ @Test
+ public void authenticatedReturnsAuthenticatedAuthorizationManagerByDefault() {
+ AuthorizationManagerFactory factory = new DefaultAuthorizationManagerFactory<>();
+ AuthorizationManager authorizationManager = factory.authenticated();
+ assertThat(authorizationManager).isInstanceOf(AuthenticatedAuthorizationManager.class);
+ }
+
+ @Test
+ public void fullyAuthenticatedReturnsAuthenticatedAuthorizationManagerByDefault() {
+ AuthorizationManagerFactory factory = new DefaultAuthorizationManagerFactory<>();
+ AuthorizationManager authorizationManager = factory.fullyAuthenticated();
+ assertThat(authorizationManager).isInstanceOf(AuthenticatedAuthorizationManager.class);
+ }
+
+ @Test
+ public void rememberMeReturnsAuthenticatedAuthorizationManagerByDefault() {
+ AuthorizationManagerFactory factory = new DefaultAuthorizationManagerFactory<>();
+ AuthorizationManager authorizationManager = factory.rememberMe();
+ assertThat(authorizationManager).isInstanceOf(AuthenticatedAuthorizationManager.class);
+ }
+
+ @Test
+ public void anonymousReturnsAuthenticatedAuthorizationManagerByDefault() {
+ AuthorizationManagerFactory factory = new DefaultAuthorizationManagerFactory<>();
+ AuthorizationManager authorizationManager = factory.anonymous();
+ assertThat(authorizationManager).isInstanceOf(AuthenticatedAuthorizationManager.class);
+ }
+
+}
diff --git a/data/src/main/java/org/springframework/security/data/repository/query/SecurityEvaluationContextExtension.java b/data/src/main/java/org/springframework/security/data/repository/query/SecurityEvaluationContextExtension.java
index 4ea5b86950..4a33013159 100644
--- a/data/src/main/java/org/springframework/security/data/repository/query/SecurityEvaluationContextExtension.java
+++ b/data/src/main/java/org/springframework/security/data/repository/query/SecurityEvaluationContextExtension.java
@@ -24,6 +24,8 @@
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.authentication.AuthenticationTrustResolver;
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.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -95,14 +97,12 @@ public class SecurityEvaluationContextExtension implements EvaluationContextExte
private Authentication authentication;
- private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
+ private final DefaultAuthorizationManagerFactory