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 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 matchers) { + AuthorizedUrl(List matchers, + AuthorizationManagerFactory authorizationManagerFactory) { this.matchers = matchers; + this.authorizationManagerFactory = authorizationManagerFactory; } protected List 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 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 defaultAuthorizationManagerFactory = new DefaultAuthorizationManagerFactory<>(); - private RoleHierarchy roleHierarchy = new NullRoleHierarchy(); + private AuthorizationManagerFactory authorizationManagerFactory = this.defaultAuthorizationManagerFactory; private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator(); - private String defaultRolePrefix = "ROLE_"; - /** * Creates a new instance that uses the current {@link Authentication} found on the * {@link org.springframework.security.core.context.SecurityContextHolder}. @@ -124,14 +124,12 @@ public String getExtensionId() { } @Override - public SecurityExpressionRoot getRootObject() { + public SecurityExpressionRoot getRootObject() { Authentication authentication = getAuthentication(); - SecurityExpressionRoot root = new SecurityExpressionRoot(authentication) { + SecurityExpressionRoot root = new SecurityExpressionRoot<>(() -> authentication, new Object()) { }; - root.setTrustResolver(this.trustResolver); - root.setRoleHierarchy(this.roleHierarchy); root.setPermissionEvaluator(this.permissionEvaluator); - root.setDefaultRolePrefix(this.defaultRolePrefix); + root.setAuthorizationManagerFactory(this.authorizationManagerFactory); return root; } @@ -154,15 +152,30 @@ private Authentication 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 authorizationManagerFactory) { + Assert.notNull(authorizationManagerFactory, "authorizationManagerFactory cannot be null"); + this.authorizationManagerFactory = authorizationManagerFactory; + } + /** * Sets the {@link AuthenticationTrustResolver} to be used. Default is * {@link AuthenticationTrustResolverImpl}. Cannot be null. * @param trustResolver the {@link AuthenticationTrustResolver} to use * @since 5.8 + * @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; + this.defaultAuthorizationManagerFactory.setTrustResolver(trustResolver); } /** @@ -170,10 +183,13 @@ public void setTrustResolver(AuthenticationTrustResolver trustResolver) { * Cannot be null. * @param roleHierarchy the {@link RoleHierarchy} to use * @since 5.8 + * @deprecated use + * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead */ + @Deprecated(since = "7.0") public void setRoleHierarchy(RoleHierarchy roleHierarchy) { Assert.notNull(roleHierarchy, "roleHierarchy cannot be null"); - this.roleHierarchy = roleHierarchy; + this.defaultAuthorizationManagerFactory.setRoleHierarchy(roleHierarchy); } /** @@ -197,9 +213,12 @@ public void setPermissionEvaluator(PermissionEvaluator permissionEvaluator) { * @param defaultRolePrefix the default prefix to add to roles. The default is * "ROLE_". * @since 5.8 + * @deprecated use + * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead */ + @Deprecated(since = "7.0") public void setDefaultRolePrefix(String defaultRolePrefix) { - this.defaultRolePrefix = defaultRolePrefix; + this.defaultAuthorizationManagerFactory.setRolePrefix(defaultRolePrefix); } } diff --git a/messaging/src/main/java/org/springframework/security/messaging/access/expression/DefaultMessageSecurityExpressionHandler.java b/messaging/src/main/java/org/springframework/security/messaging/access/expression/DefaultMessageSecurityExpressionHandler.java index fbb14a6191..bb5433bca8 100644 --- a/messaging/src/main/java/org/springframework/security/messaging/access/expression/DefaultMessageSecurityExpressionHandler.java +++ b/messaging/src/main/java/org/springframework/security/messaging/access/expression/DefaultMessageSecurityExpressionHandler.java @@ -25,9 +25,7 @@ import org.springframework.security.access.expression.SecurityExpressionHandler; import org.springframework.security.access.expression.SecurityExpressionOperations; import org.springframework.security.authentication.AuthenticationTrustResolver; -import org.springframework.security.authentication.AuthenticationTrustResolverImpl; import org.springframework.security.core.Authentication; -import org.springframework.util.Assert; /** * The default implementation of {@link SecurityExpressionHandler} which uses a @@ -40,8 +38,6 @@ */ public class DefaultMessageSecurityExpressionHandler extends AbstractSecurityExpressionHandler> { - private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); - @Override public EvaluationContext createEvaluationContext(Supplier authentication, Message message) { MessageSecurityExpressionRoot root = createSecurityExpressionRoot(authentication, message); @@ -60,14 +56,15 @@ private MessageSecurityExpressionRoot createSecurityExpressionRoot(Supplier invocation) { MessageSecurityExpressionRoot root = new MessageSecurityExpressionRoot(authentication, invocation); root.setPermissionEvaluator(getPermissionEvaluator()); - root.setTrustResolver(this.trustResolver); - root.setRoleHierarchy(getRoleHierarchy()); + // Since MessageSecurityExpressionRoot is of a different type, use the default + // factory instead of the one configured with this class. + root.setTrustResolver(getDefaultAuthorizationManagerFactory().getTrustResolver()); + root.setRoleHierarchy(getDefaultAuthorizationManagerFactory().getRoleHierarchy()); return root; } public void setTrustResolver(AuthenticationTrustResolver trustResolver) { - Assert.notNull(trustResolver, "trustResolver cannot be null"); - this.trustResolver = trustResolver; + getDefaultAuthorizationManagerFactory().setTrustResolver(trustResolver); } } diff --git a/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageSecurityExpressionRoot.java b/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageSecurityExpressionRoot.java index 594a9bcbd2..b023513d3a 100644 --- a/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageSecurityExpressionRoot.java +++ b/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageSecurityExpressionRoot.java @@ -29,7 +29,7 @@ * @author Evgeniy Cheban * @since 4.0 */ -public class MessageSecurityExpressionRoot extends SecurityExpressionRoot { +public class MessageSecurityExpressionRoot extends SecurityExpressionRoot> { public final Message message; @@ -45,7 +45,7 @@ public MessageSecurityExpressionRoot(Authentication authentication, Message m * @since 5.8 */ public MessageSecurityExpressionRoot(Supplier authentication, Message message) { - super(authentication); + super(authentication, message); this.message = message; } diff --git a/web/src/main/java/org/springframework/security/web/access/expression/DefaultHttpSecurityExpressionHandler.java b/web/src/main/java/org/springframework/security/web/access/expression/DefaultHttpSecurityExpressionHandler.java index 28abd48946..c56dfdec4d 100644 --- a/web/src/main/java/org/springframework/security/web/access/expression/DefaultHttpSecurityExpressionHandler.java +++ b/web/src/main/java/org/springframework/security/web/access/expression/DefaultHttpSecurityExpressionHandler.java @@ -25,9 +25,9 @@ import org.springframework.security.access.expression.SecurityExpressionOperations; 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.web.access.intercept.RequestAuthorizationContext; -import org.springframework.util.Assert; /** * A {@link SecurityExpressionHandler} that uses a {@link RequestAuthorizationContext} to @@ -39,10 +39,6 @@ public class DefaultHttpSecurityExpressionHandler extends AbstractSecurityExpressionHandler implements SecurityExpressionHandler { - private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); - - private String defaultRolePrefix = "ROLE_"; - @Override public EvaluationContext createEvaluationContext(Supplier authentication, RequestAuthorizationContext context) { @@ -61,11 +57,9 @@ protected SecurityExpressionOperations createSecurityExpressionRoot(Authenticati private WebSecurityExpressionRoot createSecurityExpressionRoot(Supplier 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.setTrustResolver(this.trustResolver); - root.setDefaultRolePrefix(this.defaultRolePrefix); return root; } @@ -73,10 +67,12 @@ private WebSecurityExpressionRoot createSecurityExpressionRoot(Supplier implements SecurityExpressionHandler { - private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); - - private String defaultRolePrefix = "ROLE_"; - @Override protected SecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, FilterInvocation fi) { WebSecurityExpressionRoot root = new WebSecurityExpressionRoot(authentication, fi); root.setPermissionEvaluator(getPermissionEvaluator()); - root.setTrustResolver(this.trustResolver); - root.setRoleHierarchy(getRoleHierarchy()); - root.setDefaultRolePrefix(this.defaultRolePrefix); + // Since WebSecurityExpressionRoot is of a different type, use the default factory + // instead of the one configured with this class. + root.setTrustResolver(getDefaultAuthorizationManagerFactory().getTrustResolver()); + root.setRoleHierarchy(getDefaultAuthorizationManagerFactory().getRoleHierarchy()); + root.setDefaultRolePrefix(getDefaultAuthorizationManagerFactory().getRolePrefix()); return root; } @@ -55,8 +52,7 @@ protected SecurityExpressionOperations createSecurityExpressionRoot(Authenticati * null. */ public void setTrustResolver(AuthenticationTrustResolver trustResolver) { - Assert.notNull(trustResolver, "trustResolver cannot be null"); - this.trustResolver = trustResolver; + getDefaultAuthorizationManagerFactory().setTrustResolver(trustResolver); } /** @@ -75,7 +71,7 @@ public void setTrustResolver(AuthenticationTrustResolver trustResolver) { * @param defaultRolePrefix the default prefix to add to roles. Default "ROLE_". */ public void setDefaultRolePrefix(String defaultRolePrefix) { - this.defaultRolePrefix = defaultRolePrefix; + getDefaultAuthorizationManagerFactory().setRolePrefix(defaultRolePrefix); } } diff --git a/web/src/main/java/org/springframework/security/web/access/expression/WebSecurityExpressionRoot.java b/web/src/main/java/org/springframework/security/web/access/expression/WebSecurityExpressionRoot.java index c606887ca6..37664b108a 100644 --- a/web/src/main/java/org/springframework/security/web/access/expression/WebSecurityExpressionRoot.java +++ b/web/src/main/java/org/springframework/security/web/access/expression/WebSecurityExpressionRoot.java @@ -23,6 +23,7 @@ import org.springframework.security.access.expression.SecurityExpressionRoot; import org.springframework.security.core.Authentication; import org.springframework.security.web.FilterInvocation; +import org.springframework.security.web.access.intercept.RequestAuthorizationContext; import org.springframework.security.web.util.matcher.IpAddressMatcher; /** @@ -30,7 +31,7 @@ * @author Evgeniy Cheban * @since 3.0 */ -public class WebSecurityExpressionRoot extends SecurityExpressionRoot { +public class WebSecurityExpressionRoot extends SecurityExpressionRoot { /** * Allows direct access to the request object @@ -38,7 +39,7 @@ public class WebSecurityExpressionRoot extends SecurityExpressionRoot { public final HttpServletRequest request; public WebSecurityExpressionRoot(Authentication a, FilterInvocation fi) { - this(() -> a, fi.getRequest()); + this(() -> a, new RequestAuthorizationContext(fi.getRequest())); } /** @@ -47,12 +48,27 @@ public WebSecurityExpressionRoot(Authentication a, FilterInvocation fi) { * @param authentication the {@link Supplier} of the {@link Authentication} to use * @param request the {@link HttpServletRequest} to use * @since 5.8 + * @deprecated Use + * {@link #WebSecurityExpressionRoot(Supplier, RequestAuthorizationContext)} instead */ + @Deprecated(since = "7.0") public WebSecurityExpressionRoot(Supplier authentication, HttpServletRequest request) { - super(authentication); + super(authentication, new RequestAuthorizationContext(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 5.8 + */ + public WebSecurityExpressionRoot(Supplier 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 * 202.24.0.0/14).