Add AuthorizationManagerFactory

Signed-off-by: Steve Riesenberg <5248162+sjohnr@users.noreply.github.com>
This commit is contained in:
Steve Riesenberg 2025-09-02 12:47:53 -05:00 committed by Rob Winch
parent a4f813ab29
commit eeb4574bb3
37 changed files with 2719 additions and 178 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,124 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization;
import org.jspecify.annotations.Nullable;
/**
* A factory for creating different kinds of {@link AuthorizationManager} instances.
*
* @param <T> the type of object that the authorization check is being done on
* @author Steve Riesenberg
* @since 7.0
*/
public interface AuthorizationManagerFactory<T extends @Nullable Object> {
/**
* Create an {@link AuthorizationManager} that allows anyone.
* @return A new {@link AuthorizationManager} instance
*/
default AuthorizationManager<T> permitAll() {
return SingleResultAuthorizationManager.permitAll();
}
/**
* Creates an {@link AuthorizationManager} that does not allow anyone.
* @return A new {@link AuthorizationManager} instance
*/
default AuthorizationManager<T> denyAll() {
return SingleResultAuthorizationManager.denyAll();
}
/**
* Creates an {@link AuthorizationManager} that requires users to have the specified
* role.
* @param role the role (automatically prepended with ROLE_) that should be required
* to allow access (i.e. USER, ADMIN, etc.)
* @return A new {@link AuthorizationManager} instance
*/
default AuthorizationManager<T> hasRole(String role) {
return AuthorityAuthorizationManager.hasRole(role);
}
/**
* Creates an {@link AuthorizationManager} that requires users to have one of many
* roles.
* @param roles the roles (automatically prepended with ROLE_) that the user should
* have at least one of to allow access (i.e. USER, ADMIN, etc.)
* @return A new {@link AuthorizationManager} instance
*/
default AuthorizationManager<T> hasAnyRole(String... roles) {
return AuthorityAuthorizationManager.hasAnyRole(roles);
}
/**
* Creates an {@link AuthorizationManager} that requires users to have the specified
* authority.
* @param authority the authority that should be required to allow access (i.e.
* ROLE_USER, ROLE_ADMIN, etc.)
* @return A new {@link AuthorizationManager} instance
*/
default AuthorizationManager<T> hasAuthority(String authority) {
return AuthorityAuthorizationManager.hasAuthority(authority);
}
/**
* Creates an {@link AuthorizationManager} that requires users to have one of many
* authorities.
* @param authorities the authorities that the user should have at least one of to
* allow access (i.e. ROLE_USER, ROLE_ADMIN, etc.)
* @return A new {@link AuthorizationManager} instance
*/
default AuthorizationManager<T> hasAnyAuthority(String... authorities) {
return AuthorityAuthorizationManager.hasAnyAuthority(authorities);
}
/**
* Creates an {@link AuthorizationManager} that allows any authenticated user.
* @return A new {@link AuthorizationManager} instance
*/
default AuthorizationManager<T> authenticated() {
return AuthenticatedAuthorizationManager.authenticated();
}
/**
* Creates an {@link AuthorizationManager} that allows users who have authenticated
* and were not remembered.
* @return A new {@link AuthorizationManager} instance
*/
default AuthorizationManager<T> fullyAuthenticated() {
return AuthenticatedAuthorizationManager.fullyAuthenticated();
}
/**
* Creates an {@link AuthorizationManager} that allows users that have been
* remembered.
* @return A new {@link AuthorizationManager} instance
*/
default AuthorizationManager<T> rememberMe() {
return AuthenticatedAuthorizationManager.rememberMe();
}
/**
* Creates an {@link AuthorizationManager} that allows only anonymous users.
* @return A new {@link AuthorizationManager} instance
*/
default AuthorizationManager<T> anonymous() {
return AuthenticatedAuthorizationManager.anonymous();
}
}

View File

@ -0,0 +1,123 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization;
import org.jspecify.annotations.Nullable;
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.util.Assert;
/**
* A factory for creating different kinds of {@link AuthorizationManager} instances.
*
* @param <T> the type of object that the authorization check is being done on
* @author Steve Riesenberg
* @since 7.0
*/
public final class DefaultAuthorizationManagerFactory<T extends @Nullable Object>
implements AuthorizationManagerFactory<T> {
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
private RoleHierarchy roleHierarchy = new NullRoleHierarchy();
private String rolePrefix = "ROLE_";
/**
* Sets the {@link AuthenticationTrustResolver} used to check the user's
* authentication.
* @param trustResolver the {@link AuthenticationTrustResolver} to use
*/
public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
Assert.notNull(trustResolver, "trustResolver cannot be null");
this.trustResolver = trustResolver;
}
/**
* Sets the {@link RoleHierarchy} used to discover reachable authorities.
* @param roleHierarchy the {@link RoleHierarchy} to use
*/
public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
Assert.notNull(roleHierarchy, "roleHierarchy cannot be null");
this.roleHierarchy = roleHierarchy;
}
/**
* Sets the prefix used to create an authority name from a role name. Can be an empty
* string.
* @param rolePrefix the role prefix to use
*/
public void setRolePrefix(String rolePrefix) {
Assert.notNull(rolePrefix, "rolePrefix cannot be null");
this.rolePrefix = rolePrefix;
}
@Override
public AuthorizationManager<T> hasRole(String role) {
return hasAnyRole(role);
}
@Override
public AuthorizationManager<T> hasAnyRole(String... roles) {
return withRoleHierarchy(AuthorityAuthorizationManager.hasAnyRole(this.rolePrefix, roles));
}
@Override
public AuthorizationManager<T> hasAuthority(String authority) {
return withRoleHierarchy(AuthorityAuthorizationManager.hasAuthority(authority));
}
@Override
public AuthorizationManager<T> hasAnyAuthority(String... authorities) {
return withRoleHierarchy(AuthorityAuthorizationManager.hasAnyAuthority(authorities));
}
@Override
public AuthorizationManager<T> authenticated() {
return withTrustResolver(AuthenticatedAuthorizationManager.authenticated());
}
@Override
public AuthorizationManager<T> fullyAuthenticated() {
return withTrustResolver(AuthenticatedAuthorizationManager.fullyAuthenticated());
}
@Override
public AuthorizationManager<T> rememberMe() {
return withTrustResolver(AuthenticatedAuthorizationManager.rememberMe());
}
@Override
public AuthorizationManager<T> anonymous() {
return withTrustResolver(AuthenticatedAuthorizationManager.anonymous());
}
private AuthorityAuthorizationManager<T> withRoleHierarchy(AuthorityAuthorizationManager<T> authorizationManager) {
authorizationManager.setRoleHierarchy(this.roleHierarchy);
return authorizationManager;
}
private AuthenticatedAuthorizationManager<T> withTrustResolver(
AuthenticatedAuthorizationManager<T> authorizationManager) {
authorizationManager.setTrustResolver(this.trustResolver);
return authorizationManager;
}
}

View File

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

View File

@ -0,0 +1,100 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link AuthorizationManagerFactory}.
*
* @author Steve Riesenberg
*/
public class AuthorizationManagerFactoryTests {
@Test
public void permitAllReturnsSingleResultAuthorizationManagerByDefault() {
AuthorizationManagerFactory<String> factory = new DefaultAuthorizationManagerFactory<>();
AuthorizationManager<String> authorizationManager = factory.permitAll();
assertThat(authorizationManager).isInstanceOf(SingleResultAuthorizationManager.class);
}
@Test
public void denyAllReturnsSingleResultAuthorizationManagerByDefault() {
AuthorizationManagerFactory<String> factory = new DefaultAuthorizationManagerFactory<>();
AuthorizationManager<String> authorizationManager = factory.denyAll();
assertThat(authorizationManager).isInstanceOf(SingleResultAuthorizationManager.class);
}
@Test
public void hasRoleReturnsAuthorityAuthorizationManagerByDefault() {
AuthorizationManagerFactory<String> factory = new DefaultAuthorizationManagerFactory<>();
AuthorizationManager<String> authorizationManager = factory.hasRole("USER");
assertThat(authorizationManager).isInstanceOf(AuthorityAuthorizationManager.class);
}
@Test
public void hasAnyRoleReturnsAuthorityAuthorizationManagerByDefault() {
AuthorizationManagerFactory<String> factory = new DefaultAuthorizationManagerFactory<>();
AuthorizationManager<String> authorizationManager = factory.hasAnyRole("USER", "ADMIN");
assertThat(authorizationManager).isInstanceOf(AuthorityAuthorizationManager.class);
}
@Test
public void hasAuthorityReturnsAuthorityAuthorizationManagerByDefault() {
AuthorizationManagerFactory<String> factory = new DefaultAuthorizationManagerFactory<>();
AuthorizationManager<String> authorizationManager = factory.hasAuthority("authority1");
assertThat(authorizationManager).isInstanceOf(AuthorityAuthorizationManager.class);
}
@Test
public void hasAnyAuthorityReturnsAuthorityAuthorizationManagerByDefault() {
AuthorizationManagerFactory<String> factory = new DefaultAuthorizationManagerFactory<>();
AuthorizationManager<String> authorizationManager = factory.hasAnyAuthority("authority1", "authority2");
assertThat(authorizationManager).isInstanceOf(AuthorityAuthorizationManager.class);
}
@Test
public void authenticatedReturnsAuthenticatedAuthorizationManagerByDefault() {
AuthorizationManagerFactory<String> factory = new DefaultAuthorizationManagerFactory<>();
AuthorizationManager<String> authorizationManager = factory.authenticated();
assertThat(authorizationManager).isInstanceOf(AuthenticatedAuthorizationManager.class);
}
@Test
public void fullyAuthenticatedReturnsAuthenticatedAuthorizationManagerByDefault() {
AuthorizationManagerFactory<String> factory = new DefaultAuthorizationManagerFactory<>();
AuthorizationManager<String> authorizationManager = factory.fullyAuthenticated();
assertThat(authorizationManager).isInstanceOf(AuthenticatedAuthorizationManager.class);
}
@Test
public void rememberMeReturnsAuthenticatedAuthorizationManagerByDefault() {
AuthorizationManagerFactory<String> factory = new DefaultAuthorizationManagerFactory<>();
AuthorizationManager<String> authorizationManager = factory.rememberMe();
assertThat(authorizationManager).isInstanceOf(AuthenticatedAuthorizationManager.class);
}
@Test
public void anonymousReturnsAuthenticatedAuthorizationManagerByDefault() {
AuthorizationManagerFactory<String> factory = new DefaultAuthorizationManagerFactory<>();
AuthorizationManager<String> authorizationManager = factory.anonymous();
assertThat(authorizationManager).isInstanceOf(AuthenticatedAuthorizationManager.class);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,77 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain clients copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.servlet.authorization.authzauthorizationmanagerfactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authorization.AuthorizationManagerFactory;
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
/**
* Documentation for {@link AuthorizationManagerFactory}.
*
* @author Steve Riesenberg
*/
@Configuration(proxyBeanMethods = false)
public class AuthorizationManagerFactoryConfiguration {
// tag::config[]
@Bean
<T> AuthorizationManagerFactory<T> authorizationManagerFactory() {
DefaultAuthorizationManagerFactory<T> authorizationManagerFactory =
new DefaultAuthorizationManagerFactory<>();
authorizationManagerFactory.setTrustResolver(getAuthenticationTrustResolver());
authorizationManagerFactory.setRoleHierarchy(getRoleHierarchy());
authorizationManagerFactory.setRolePrefix("role_");
return authorizationManagerFactory;
}
// end::config[]
private static AuthenticationTrustResolverImpl getAuthenticationTrustResolver() {
AuthenticationTrustResolverImpl authenticationTrustResolver =
new AuthenticationTrustResolverImpl();
authenticationTrustResolver.setAnonymousClass(Anonymous.class);
authenticationTrustResolver.setRememberMeClass(RememberMe.class);
return authenticationTrustResolver;
}
private static RoleHierarchyImpl getRoleHierarchy() {
return RoleHierarchyImpl.fromHierarchy("role_admin > role_user");
}
static class Anonymous extends TestingAuthenticationToken {
Anonymous(String principal) {
super(principal, "", "role_anonymous");
}
}
static class RememberMe extends TestingAuthenticationToken {
RememberMe(String principal) {
super(principal, "", "role_rememberMe");
}
}
}

View File

@ -0,0 +1,208 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain clients copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.servlet.authorization.authzauthorizationmanagerfactory;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.Authentication;
import org.springframework.security.docs.servlet.authorization.authzauthorizationmanagerfactory.AuthorizationManagerFactoryConfiguration.Anonymous;
import org.springframework.security.docs.servlet.authorization.authzauthorizationmanagerfactory.AuthorizationManagerFactoryConfiguration.RememberMe;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for {@link AuthorizationManagerFactoryConfiguration}.
*
* @author Steve Riesenberg
*/
@ExtendWith(SpringTestContextExtension.class)
public class AuthorizationManagerFactoryConfigurationTests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
MockMvc mockMvc;
@Test
void getAnonymousWhenCustomAnonymousClassThenOk() throws Exception {
this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
TestController.class).autowire();
Authentication authentication = new Anonymous("anonymous");
// @formatter:off
this.mockMvc.perform(get("/anonymous").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getAnonymousWhenAuthenticatedThenForbidden() throws Exception {
this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("user", "", "role_user");
// @formatter:off
this.mockMvc.perform(get("/anonymous").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getRememberMeWhenCustomRememberMeClassThenOk() throws Exception {
this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
TestController.class).autowire();
Authentication authentication = new RememberMe("rememberMe");
// @formatter:off
this.mockMvc.perform(get("/rememberMe").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getRememberMeWhenAuthenticatedThenForbidden() throws Exception {
this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("user", "", "role_user");
// @formatter:off
this.mockMvc.perform(get("/rememberMe").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getUserWhenCustomUserRoleThenOk() throws Exception {
this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("user", "", "role_user");
// @formatter:off
this.mockMvc.perform(get("/user").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getUserWhenCustomAdminRoleThenOk() throws Exception {
this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("admin", "", "role_admin");
// @formatter:off
this.mockMvc.perform(get("/user").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getPreAuthorizeWhenCustomUserRoleThenOk() throws Exception {
this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("user", "", "role_user");
// @formatter:off
this.mockMvc.perform(get("/preAuthorize").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getPreAuthorizeWhenCustomAdminRoleThenOk() throws Exception {
this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("admin", "", "role_admin");
// @formatter:off
this.mockMvc.perform(get("/preAuthorize").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getPreAuthorizeWhenOtherRoleThenForbidden() throws Exception {
this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("other", "", "role_other");
// @formatter:off
this.mockMvc.perform(get("/preAuthorize").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@EnableWebMvc
@EnableWebSecurity
@EnableMethodSecurity
@Configuration
static class SecurityConfiguration {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/anonymous").anonymous()
.requestMatchers("/rememberMe").rememberMe()
.requestMatchers("/user").hasRole("user")
.requestMatchers("/preAuthorize").permitAll()
.anyRequest().denyAll()
);
// @formatter:on
return http.build();
}
}
@RestController
static class TestController {
@GetMapping({ "/anonymous", "/rememberMe", "/user" })
@ResponseStatus(HttpStatus.OK)
void httpRequest() {
}
@GetMapping("/preAuthorize")
@ResponseStatus(HttpStatus.OK)
@PreAuthorize("hasRole('user')")
void preAuthorize() {
}
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain clients copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.servlet.authorization.customizingauthorizationmanagers;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationManagerFactory;
import org.springframework.security.authorization.AuthorizationManagers;
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
import org.springframework.stereotype.Component;
/**
* Documentation for {@link AuthorizationManagerFactory}.
*
* @author Steve Riesenberg
*/
// tag::class[]
@Component
public class CustomHttpRequestsAuthorizationManagerFactory
implements AuthorizationManagerFactory<RequestAuthorizationContext> {
private final AuthorizationManagerFactory<RequestAuthorizationContext> delegate =
new DefaultAuthorizationManagerFactory<>();
@Override
public AuthorizationManager<RequestAuthorizationContext> authenticated() {
return AuthorizationManagers.allOf(
this.delegate.authenticated(),
this.delegate.hasRole("USER")
);
}
}
// end::class[]

View File

@ -0,0 +1,137 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain clients copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.servlet.authorization.customizingauthorizationmanagers;
import java.util.Collections;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for {@link CustomHttpRequestsAuthorizationManagerFactory}.
*
* @author Steve Riesenberg
*/
@ExtendWith(SpringTestContextExtension.class)
public class CustomHttpRequestsAuthorizationManagerFactoryTests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
MockMvc mockMvc;
@Test
void getHelloWhenAnonymousThenForbidden() throws Exception {
this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
// @formatter:off
this.mockMvc.perform(get("/hello").with(anonymous()))
.andExpect(status().isForbidden())
.andExpect(unauthenticated());
// @formatter:on
}
@Test
void getHelloWhenAuthenticatedWithNoRolesThenForbidden() throws Exception {
this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("user", "", Collections.emptyList());
// @formatter:off
this.mockMvc.perform(get("/hello").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getHelloWhenAuthenticatedWithUserRoleThenOk() throws Exception {
this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("user", "", "ROLE_USER");
// @formatter:off
this.mockMvc.perform(get("/hello").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getHelloWhenAuthenticatedWithOtherRoleThenForbidden() throws Exception {
this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("user", "", "ROLE_OTHER");
// @formatter:off
this.mockMvc.perform(get("/hello").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@EnableWebMvc
@EnableWebSecurity
@Configuration
static class SecurityConfiguration {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
);
// @formatter:on
return http.build();
}
@Bean
CustomHttpRequestsAuthorizationManagerFactory customHttpRequestsAuthorizationManagerFactory() {
return new CustomHttpRequestsAuthorizationManagerFactory();
}
}
@RestController
static class TestController {
@GetMapping("/**")
@ResponseStatus(HttpStatus.OK)
void ok() {
}
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain clients copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.servlet.authorization.customizingauthorizationmanagers;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationManagerFactory;
import org.springframework.security.authorization.AuthorizationManagers;
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
import org.springframework.stereotype.Component;
/**
* Documentation for {@link AuthorizationManagerFactory}.
*
* @author Steve Riesenberg
*/
// tag::class[]
@Component
public class CustomMethodInvocationAuthorizationManagerFactory
implements AuthorizationManagerFactory<MethodInvocation> {
private final AuthorizationManagerFactory<MethodInvocation> delegate =
new DefaultAuthorizationManagerFactory<>();
@Override
public AuthorizationManager<MethodInvocation> hasRole(String role) {
return AuthorizationManagers.anyOf(
this.delegate.hasRole(role),
this.delegate.hasRole("ADMIN")
);
}
@Override
public AuthorizationManager<MethodInvocation> hasAnyRole(String... roles) {
return AuthorizationManagers.anyOf(
this.delegate.hasAnyRole(roles),
this.delegate.hasRole("ADMIN")
);
}
}
// end::class[]

View File

@ -0,0 +1,191 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain clients copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.servlet.authorization.customizingauthorizationmanagers;
import java.util.Collections;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for {@link CustomMethodInvocationAuthorizationManagerFactory}.
*
* @author Steve Riesenberg
*/
@ExtendWith(SpringTestContextExtension.class)
public class CustomMethodInvocationAuthorizationManagerFactoryTests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
MockMvc mockMvc;
@Test
void getUserWhenAnonymousThenForbidden() throws Exception {
this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
// @formatter:off
this.mockMvc.perform(get("/user").with(anonymous()))
.andExpect(status().isForbidden())
.andExpect(unauthenticated());
// @formatter:on
}
@Test
void getUserWhenAuthenticatedWithNoRolesThenForbidden() throws Exception {
this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("user", "", Collections.emptyList());
// @formatter:off
this.mockMvc.perform(get("/user").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getUserWhenAuthenticatedWithUserRoleThenOk() throws Exception {
this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("user", "", "ROLE_USER");
// @formatter:off
this.mockMvc.perform(get("/user").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getUserWhenAuthenticatedWithAdminRoleThenOk() throws Exception {
this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("admin", "", "ROLE_ADMIN");
// @formatter:off
this.mockMvc.perform(get("/user").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getUserWhenAuthenticatedWithOtherRoleThenForbidden() throws Exception {
this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("user", "", "ROLE_OTHER");
// @formatter:off
this.mockMvc.perform(get("/user").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getRolesWhenAuthenticatedWithRole1RoleThenOk() throws Exception {
this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("user", "", "ROLE_ROLE1");
// @formatter:off
this.mockMvc.perform(get("/roles").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getRolesWhenAuthenticatedWithAdminRoleThenOk() throws Exception {
this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("admin", "", "ROLE_ADMIN");
// @formatter:off
this.mockMvc.perform(get("/roles").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getRolesWhenAuthenticatedWithOtherRoleThenForbidden() throws Exception {
this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("user", "", "ROLE_OTHER");
// @formatter:off
this.mockMvc.perform(get("/roles").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@EnableWebMvc
@EnableWebSecurity
@EnableMethodSecurity
@Configuration
static class SecurityConfiguration {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
);
// @formatter:on
return http.build();
}
@Bean
CustomMethodInvocationAuthorizationManagerFactory customMethodInvocationAuthorizationManagerFactory() {
return new CustomMethodInvocationAuthorizationManagerFactory();
}
}
@RestController
static class TestController {
@GetMapping("/user")
@ResponseStatus(HttpStatus.OK)
@PreAuthorize("hasRole('USER')")
void user() {
}
@GetMapping("/roles")
@ResponseStatus(HttpStatus.OK)
@PreAuthorize("hasAnyRole('ROLE1', 'ROLE2')")
void roles() {
}
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.kt.docs.servlet.authorization.authzauthorizationmanagerfactory
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl
import org.springframework.security.authentication.AuthenticationTrustResolverImpl
import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.authorization.AuthorizationManagerFactory
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory
/**
* Documentation for [org.springframework.security.authorization.AuthorizationManagerFactory].
*
* @author Steve Riesenberg
*/
@Configuration(proxyBeanMethods = false)
class AuthorizationManagerFactoryConfiguration {
// tag::config[]
@Bean
fun <T> authorizationManagerFactory(): AuthorizationManagerFactory<T> {
val authorizationManagerFactory = DefaultAuthorizationManagerFactory<T>()
authorizationManagerFactory.setTrustResolver(getAuthenticationTrustResolver())
authorizationManagerFactory.setRoleHierarchy(getRoleHierarchy())
authorizationManagerFactory.setRolePrefix("role_")
return authorizationManagerFactory
}
// end::config[]
private fun getAuthenticationTrustResolver(): AuthenticationTrustResolverImpl {
val authenticationTrustResolver = AuthenticationTrustResolverImpl()
authenticationTrustResolver.setAnonymousClass(Anonymous::class.java)
authenticationTrustResolver.setRememberMeClass(RememberMe::class.java)
return authenticationTrustResolver
}
private fun getRoleHierarchy(): RoleHierarchyImpl {
return RoleHierarchyImpl.fromHierarchy("role_admin > role_user")
}
internal class Anonymous(principal: String) :
TestingAuthenticationToken(principal, "", "role_anonymous")
internal class RememberMe(principal: String) :
TestingAuthenticationToken(principal, "", "role_rememberMe")
}

View File

@ -0,0 +1,229 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.kt.docs.servlet.authorization.authzauthorizationmanagerfactory
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpStatus
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.test.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.security.kt.docs.servlet.authorization.authzauthorizationmanagerfactory.AuthorizationManagerFactoryConfiguration.Anonymous
import org.springframework.security.kt.docs.servlet.authorization.authzauthorizationmanagerfactory.AuthorizationManagerFactoryConfiguration.RememberMe
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated
import org.springframework.security.web.SecurityFilterChain
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.servlet.config.annotation.EnableWebMvc
/**
* Tests for [AuthorizationManagerFactoryConfiguration].
*
* @author Steve Riesenberg
*/
@ExtendWith(SpringTestContextExtension::class)
class AuthorizationManagerFactoryConfigurationTests {
@JvmField
val spring = SpringTestContext(this)
@Autowired
lateinit var mockMvc: MockMvc
@Test
@Throws(Exception::class)
fun getAnonymousWhenCustomAnonymousClassThenOk() {
this.spring.register(AuthorizationManagerFactoryConfiguration::class.java, SecurityConfiguration::class.java)
.autowire()
val authentication = Anonymous("anonymous")
// @formatter:off
mockMvc.perform(get("/anonymous").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getAnonymousWhenAuthenticatedThenForbidden() {
this.spring.register(AuthorizationManagerFactoryConfiguration::class.java, SecurityConfiguration::class.java)
.autowire()
val authentication = TestingAuthenticationToken("user", "", "role_user")
// @formatter:off
mockMvc.perform(get("/anonymous").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getRememberMeWhenCustomRememberMeClassThenOk() {
this.spring.register(AuthorizationManagerFactoryConfiguration::class.java, SecurityConfiguration::class.java)
.autowire()
val authentication = RememberMe("rememberMe")
// @formatter:off
mockMvc.perform(get("/rememberMe").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getRememberMeWhenAuthenticatedThenForbidden() {
this.spring.register(AuthorizationManagerFactoryConfiguration::class.java, SecurityConfiguration::class.java)
.autowire()
val user = TestingAuthenticationToken("user", "", "role_user")
// @formatter:off
mockMvc.perform(get("/rememberMe").with(authentication(user)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(user))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getUserWhenCustomUserRoleThenOk() {
this.spring.register(AuthorizationManagerFactoryConfiguration::class.java, SecurityConfiguration::class.java)
.autowire()
val authentication = TestingAuthenticationToken("user", "", "role_user")
// @formatter:off
mockMvc.perform(get("/user").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getUserWhenCustomAdminRoleThenOk() {
this.spring.register(AuthorizationManagerFactoryConfiguration::class.java, SecurityConfiguration::class.java)
.autowire()
val admin = TestingAuthenticationToken("admin", "", "role_admin")
// @formatter:off
mockMvc.perform(get("/user").with(authentication(admin)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(admin))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getPreAuthorizeWhenCustomUserRoleThenOk() {
this.spring.register(AuthorizationManagerFactoryConfiguration::class.java, SecurityConfiguration::class.java)
.autowire()
val authentication = TestingAuthenticationToken("user", "", "role_user")
// @formatter:off
mockMvc.perform(get("/preAuthorize").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getPreAuthorizeWhenCustomAdminRoleThenOk() {
this.spring.register(AuthorizationManagerFactoryConfiguration::class.java, SecurityConfiguration::class.java)
.autowire()
val authentication = TestingAuthenticationToken("admin", "", "role_admin")
// @formatter:off
mockMvc.perform(get("/preAuthorize").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getPreAuthorizeWhenOtherRoleThenForbidden() {
this.spring.register(AuthorizationManagerFactoryConfiguration::class.java, SecurityConfiguration::class.java)
.autowire()
val authentication = TestingAuthenticationToken("other", "", "role_other")
// @formatter:off
mockMvc.perform(get("/preAuthorize").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@EnableWebMvc
@EnableWebSecurity
@EnableMethodSecurity
@Configuration
internal open class SecurityConfiguration {
@Bean
@Throws(Exception::class)
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
// @formatter:off
http.authorizeHttpRequests { authorize ->
authorize
.requestMatchers("/anonymous").anonymous()
.requestMatchers("/rememberMe").rememberMe()
.requestMatchers("/user").hasRole("user")
.requestMatchers("/preAuthorize").permitAll()
.anyRequest().denyAll()
}
// @formatter:on
return http.build()
}
@Bean
open fun testController(testService: TestService): TestController {
return TestController(testService())
}
@Bean
open fun testService(): TestService {
return TestServiceImpl()
}
}
@RestController
internal open class TestController(private val testService: TestService) {
@GetMapping(value = ["/anonymous", "/rememberMe", "/user"])
@ResponseStatus(HttpStatus.OK)
fun httpRequest() {
}
@GetMapping("/preAuthorize")
@ResponseStatus(HttpStatus.OK)
fun preAuthorize() {
testService.preAuthorize()
}
}
internal interface TestService {
@PreAuthorize("hasRole('user')")
fun preAuthorize()
}
internal open class TestServiceImpl : TestService {
override fun preAuthorize() {
}
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.kt.docs.servlet.authorization.customizingauthorizationmanagers
import org.springframework.security.authorization.AuthorizationManager
import org.springframework.security.authorization.AuthorizationManagerFactory
import org.springframework.security.authorization.AuthorizationManagers
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory
import org.springframework.security.web.access.intercept.RequestAuthorizationContext
import org.springframework.stereotype.Component
/**
* Documentation for {@link AuthorizationManagerFactory}.
*
* @author Steve Riesenberg
*/
// tag::class[]
@Component
class CustomHttpRequestsAuthorizationManagerFactory : AuthorizationManagerFactory<RequestAuthorizationContext> {
private val delegate = DefaultAuthorizationManagerFactory<RequestAuthorizationContext>()
override fun authenticated(): AuthorizationManager<RequestAuthorizationContext> {
return AuthorizationManagers.allOf(
delegate.authenticated(),
delegate.hasRole("USER")
)
}
}
// end::class[]

View File

@ -0,0 +1,131 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.kt.docs.servlet.authorization.customizingauthorizationmanagers
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpStatus
import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.test.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated
import org.springframework.security.web.SecurityFilterChain
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.servlet.config.annotation.EnableWebMvc
/**
* Tests for [CustomHttpRequestsAuthorizationManagerFactory].
*
* @author Steve Riesenberg
*/
@ExtendWith(SpringTestContextExtension::class)
class CustomHttpRequestsAuthorizationManagerFactoryTests {
@JvmField
val spring = SpringTestContext(this)
@Autowired
lateinit var mockMvc: MockMvc
@Test
@Throws(Exception::class)
fun getHelloWhenAnonymousThenForbidden() {
spring.register(SecurityConfiguration::class.java, TestController::class.java).autowire()
// @formatter:off
mockMvc.perform(get("/hello").with(anonymous()))
.andExpect(status().isForbidden())
.andExpect(SecurityMockMvcResultMatchers.unauthenticated())
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getHelloWhenAuthenticatedWithUserRoleThenOk() {
spring.register(SecurityConfiguration::class.java, TestController::class.java).autowire()
val authentication = TestingAuthenticationToken("user", "", "ROLE_USER")
// @formatter:off
mockMvc.perform(get("/hello").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getHelloWhenAuthenticatedWithOtherRoleThenForbidden() {
spring.register(SecurityConfiguration::class.java, TestController::class.java).autowire()
val authentication = TestingAuthenticationToken("user", "", "ROLE_OTHER")
// @formatter:off
mockMvc.perform(get("/hello").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getHelloWhenAuthenticatedWithNoRolesThenForbidden() {
spring.register(SecurityConfiguration::class.java, TestController::class.java).autowire()
val authentication = TestingAuthenticationToken("user", "", listOf())
// @formatter:off
mockMvc.perform(get("/hello").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@EnableWebMvc
@EnableWebSecurity
@Configuration
internal open class SecurityConfiguration {
@Bean
@Throws(Exception::class)
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
// @formatter:off
http
.authorizeHttpRequests { authorize ->
authorize.anyRequest().authenticated()
}
// @formatter:on
return http.build()
}
@Bean
open fun customHttpRequestsAuthorizationManagerFactory(): CustomHttpRequestsAuthorizationManagerFactory {
return CustomHttpRequestsAuthorizationManagerFactory()
}
}
@RestController
internal class TestController {
@GetMapping("/**")
@ResponseStatus(HttpStatus.OK)
fun ok() {
}
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.kt.docs.servlet.authorization.customizingauthorizationmanagers
import org.aopalliance.intercept.MethodInvocation
import org.springframework.security.authorization.AuthorizationManager
import org.springframework.security.authorization.AuthorizationManagerFactory
import org.springframework.security.authorization.AuthorizationManagers
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory
import org.springframework.stereotype.Component
/**
* Documentation for [AuthorizationManagerFactory].
*
* @author Steve Riesenberg
*/
// tag::class[]
@Component
class CustomMethodInvocationAuthorizationManagerFactory : AuthorizationManagerFactory<MethodInvocation> {
private val delegate = DefaultAuthorizationManagerFactory<MethodInvocation>()
override fun hasRole(role: String): AuthorizationManager<MethodInvocation> {
return AuthorizationManagers.anyOf(
delegate.hasRole(role),
delegate.hasRole("ADMIN")
)
}
override fun hasAnyRole(vararg roles: String): AuthorizationManager<MethodInvocation> {
return AuthorizationManagers.anyOf(
delegate.hasAnyRole(*roles),
delegate.hasRole("ADMIN")
)
}
}
// end::class[]

View File

@ -0,0 +1,215 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.kt.docs.servlet.authorization.customizingauthorizationmanagers
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpStatus
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.test.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated
import org.springframework.security.web.SecurityFilterChain
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.servlet.config.annotation.EnableWebMvc
/**
* Tests for [CustomMethodInvocationAuthorizationManagerFactory].
*
* @author Steve Riesenberg
*/
@ExtendWith(SpringTestContextExtension::class)
class CustomMethodInvocationAuthorizationManagerFactoryTests {
@JvmField
val spring = SpringTestContext(this)
@Autowired
lateinit var mockMvc: MockMvc
@Test
@Throws(Exception::class)
fun getUserWhenAnonymousThenForbidden() {
spring.register(SecurityConfiguration::class.java).autowire()
// @formatter:off
mockMvc.perform(get("/user").with(anonymous()))
.andExpect(status().isForbidden())
.andExpect(unauthenticated())
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getUserWhenAuthenticatedWithNoRolesThenForbidden() {
spring.register(SecurityConfiguration::class.java).autowire()
val authentication = TestingAuthenticationToken("user", "", listOf())
// @formatter:off
mockMvc.perform(get("/user").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getUserWhenAuthenticatedWithUserRoleThenOk() {
spring.register(SecurityConfiguration::class.java).autowire()
val authentication = TestingAuthenticationToken("user", "", "ROLE_USER")
// @formatter:off
mockMvc.perform(get("/user").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getUserWhenAuthenticatedWithAdminRoleThenOk() {
spring.register(SecurityConfiguration::class.java).autowire()
val authentication = TestingAuthenticationToken("user", "", "ROLE_ADMIN")
// @formatter:off
mockMvc.perform(get("/user").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getUserWhenAuthenticatedWithOtherRoleThenForbidden() {
spring.register(SecurityConfiguration::class.java).autowire()
val authentication = TestingAuthenticationToken("user", "", "ROLE_OTHER")
// @formatter:off
mockMvc.perform(get("/user").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getRolesWhenAuthenticatedWithRole1RoleThenOk() {
spring.register(SecurityConfiguration::class.java).autowire()
val authentication = TestingAuthenticationToken("user", "", "ROLE_ROLE1")
// @formatter:off
mockMvc.perform(get("/roles").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getRolesWhenAuthenticatedWithAdminRoleThenOk() {
spring.register(SecurityConfiguration::class.java).autowire()
val authentication = TestingAuthenticationToken("user", "", "ROLE_ADMIN")
// @formatter:off
mockMvc.perform(get("/roles").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getRolesWhenAuthenticatedWithOtherRoleThenForbidden() {
spring.register(SecurityConfiguration::class.java).autowire()
val authentication = TestingAuthenticationToken("user", "", "ROLE_OTHER")
// @formatter:off
mockMvc.perform(get("/roles").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@EnableWebMvc
@EnableWebSecurity
@EnableMethodSecurity
@Configuration
internal open class SecurityConfiguration {
@Bean
@Throws(Exception::class)
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
// @formatter:off
http
.authorizeHttpRequests { authorize ->
authorize.anyRequest().authenticated()
}
// @formatter:on
return http.build()
}
@Bean
open fun customMethodInvocationAuthorizationManagerFactory(): CustomMethodInvocationAuthorizationManagerFactory {
return CustomMethodInvocationAuthorizationManagerFactory()
}
@Bean
open fun testController(testService: TestService): TestController {
return TestController(testService())
}
@Bean
open fun testService(): TestService {
return TestServiceImpl()
}
}
@RestController
internal open class TestController(private val testService: TestService) {
@GetMapping("/user")
@ResponseStatus(HttpStatus.OK)
fun user() {
testService.user()
}
@GetMapping("/roles")
@ResponseStatus(HttpStatus.OK)
fun roles() {
testService.roles()
}
}
internal interface TestService {
@PreAuthorize("hasRole('USER')")
fun user()
@PreAuthorize("hasAnyRole('ROLE1', 'ROLE2')")
fun roles()
}
internal open class TestServiceImpl : TestService {
override fun user() {
}
override fun roles() {
}
}
}

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2004-present the original author or authors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- tag::config[] -->
<b:bean id="authorizationManagerFactory" class="org.springframework.security.authorization.DefaultAuthorizationManagerFactory">
<b:property name="trustResolver" ref="authenticationTrustResolver"/>
<b:property name="roleHierarchy" ref="roleHierarchy"/>
<b:property name="rolePrefix" value="role_"/>
</b:bean>
<!-- end::config[] -->
<b:bean id="authenticationTrustResolver" class="org.springframework.security.authentication.AuthenticationTrustResolverImpl">
<b:property name="anonymousClass" value="org.springframework.security.authentication.TestingAuthenticationToken"/>
</b:bean>
<b:bean id="roleHierarchy" class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl" factory-method="fromHierarchy">
<b:constructor-arg name="hierarchy" value="role_admin > role_user"/>
</b:bean>
</b:beans>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,62 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.access.expression;
import java.util.function.Supplier;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.security.access.expression.SecurityExpressionRoot;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.util.matcher.IpAddressMatcher;
/**
* @author Steve Riesenberg
* @since 7.0
*/
final class FilterInvocationExpressionRoot extends SecurityExpressionRoot<FilterInvocation> {
/**
* Allows direct access to the request object
*/
public final HttpServletRequest request;
/**
* Creates an instance for the given {@link Supplier} of the {@link Authentication}
* and {@link HttpServletRequest}.
* @param authentication the {@link Supplier} of the {@link Authentication} to use
* @param fi the {@link FilterInvocation} to use
*/
FilterInvocationExpressionRoot(Supplier<Authentication> authentication, FilterInvocation fi) {
super(authentication, fi);
this.request = fi.getRequest();
}
/**
* Takes a specific IP address or a range using the IP/Netmask (e.g. 192.168.1.0/24 or
* 202.24.0.0/14).
* @param ipAddress the address or range of addresses from which the request must
* come.
* @return true if the IP address of the current request is in the required range.
*/
public boolean hasIpAddress(String ipAddress) {
IpAddressMatcher matcher = new IpAddressMatcher(ipAddress);
return matcher.matches(this.request);
}
}

View File

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