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.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationManagerFactory;
import org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
import org.springframework.security.authorization.method.MethodInvocationResult;
@ -121,6 +122,11 @@ final class PrePostMethodSecurityConfiguration implements ImportAware, Applicati
this.expressionHandler.setRoleHierarchy(roleHierarchy);
}
@Autowired(required = false)
void setAuthorizationManagerFactory(AuthorizationManagerFactory<MethodInvocation> authorizationManagerFactory) {
this.expressionHandler.setAuthorizationManagerFactory(authorizationManagerFactory);
}
@Autowired(required = false)
void setTemplateDefaults(AnnotationTemplateExpressionDefaults 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.function.Function;
import java.util.function.Supplier;
import jakarta.servlet.http.HttpServletRequest;
@ -27,13 +26,12 @@ import org.springframework.context.ApplicationContext;
import org.springframework.core.ResolvableType;
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
import org.springframework.security.authorization.AuthorityAuthorizationManager;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationManagerFactory;
import org.springframework.security.authorization.AuthorizationManagers;
import org.springframework.security.authorization.SingleResultAuthorizationManager;
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
@ -46,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.RequestMatcherEntry;
import org.springframework.util.Assert;
import org.springframework.util.function.SingletonSupplier;
/**
* Adds a URL based authorization using {@link AuthorizationManager}.
*
* @param <H> the type of {@link HttpSecurityBuilder} that is being configured.
* @author Evgeniy Cheban
* @author Steve Riesenberg
* @since 5.5
*/
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 Supplier<RoleHierarchy> roleHierarchy;
private String rolePrefix = "ROLE_";
private final AuthorizationManagerFactory<RequestAuthorizationContext> authorizationManagerFactory;
private ObjectPostProcessor<AuthorizationManager<HttpServletRequest>> postProcessor = ObjectPostProcessor
.identity();
@ -81,13 +77,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
else {
this.publisher = new SpringAuthorizationEventPublisher(context);
}
this.roleHierarchy = SingletonSupplier.of(() -> (context.getBeanNamesForType(RoleHierarchy.class).length > 0)
? context.getBean(RoleHierarchy.class) : new NullRoleHierarchy());
String[] grantedAuthorityDefaultsBeanNames = context.getBeanNamesForType(GrantedAuthorityDefaults.class);
if (grantedAuthorityDefaultsBeanNames.length > 0) {
GrantedAuthorityDefaults grantedAuthorityDefaults = context.getBean(GrantedAuthorityDefaults.class);
this.rolePrefix = grantedAuthorityDefaults.getRolePrefix();
}
this.authorizationManagerFactory = getAuthorizationManagerFactory(context);
ResolvableType type = ResolvableType.forClassWithGenerics(ObjectPostProcessor.class,
ResolvableType.forClassWithGenerics(AuthorizationManager.class, HttpServletRequest.class));
ObjectProvider<ObjectPostProcessor<AuthorizationManager<HttpServletRequest>>> provider = context
@ -95,6 +85,35 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
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
* with after applying the {@link AuthorizeHttpRequestsConfigurer}.
@ -173,7 +192,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
@Override
protected AuthorizedUrl chainRequestMatchers(List<RequestMatcher> 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 AuthorizationManagerFactory<RequestAuthorizationContext> authorizationManagerFactory;
private boolean not;
/**
* Creates an instance.
* @param matchers the {@link RequestMatcher} instances to map
* @param authorizationManagerFactory the {@link AuthorizationManagerFactory} for
* creating instances of {@link AuthorizationManager}
*/
AuthorizedUrl(List<? extends RequestMatcher> matchers) {
AuthorizedUrl(List<? extends RequestMatcher> matchers,
AuthorizationManagerFactory<RequestAuthorizationContext> authorizationManagerFactory) {
this.matchers = matchers;
this.authorizationManagerFactory = authorizationManagerFactory;
}
protected List<? extends RequestMatcher> getMatchers() {
return this.matchers;
}
void setAuthorizationManagerFactory(
AuthorizationManagerFactory<RequestAuthorizationContext> authorizationManagerFactory) {
this.authorizationManagerFactory = authorizationManagerFactory;
}
/**
* Negates the following authorization rule.
* @return the {@link AuthorizedUrl} for further customization
@ -231,7 +261,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
* customizations
*/
public AuthorizationManagerRequestMatcherRegistry permitAll() {
return access(SingleResultAuthorizationManager.permitAll());
return access(this.authorizationManagerFactory.permitAll());
}
/**
@ -240,7 +270,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
* customizations
*/
public AuthorizationManagerRequestMatcherRegistry denyAll() {
return access(SingleResultAuthorizationManager.denyAll());
return access(this.authorizationManagerFactory.denyAll());
}
/**
@ -251,8 +281,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
* customizations
*/
public AuthorizationManagerRequestMatcherRegistry hasRole(String role) {
return access(withRoleHierarchy(AuthorityAuthorizationManager
.hasAnyRole(AuthorizeHttpRequestsConfigurer.this.rolePrefix, new String[] { role })));
return access(this.authorizationManagerFactory.hasRole(role));
}
/**
@ -264,8 +293,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
* customizations
*/
public AuthorizationManagerRequestMatcherRegistry hasAnyRole(String... roles) {
return access(withRoleHierarchy(
AuthorityAuthorizationManager.hasAnyRole(AuthorizeHttpRequestsConfigurer.this.rolePrefix, roles)));
return access(this.authorizationManagerFactory.hasAnyRole(roles));
}
/**
@ -275,7 +303,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
* customizations
*/
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
*/
public AuthorizationManagerRequestMatcherRegistry hasAnyAuthority(String... authorities) {
return access(withRoleHierarchy(AuthorityAuthorizationManager.hasAnyAuthority(authorities)));
}
private AuthorityAuthorizationManager<RequestAuthorizationContext> withRoleHierarchy(
AuthorityAuthorizationManager<RequestAuthorizationContext> manager) {
manager.setRoleHierarchy(AuthorizeHttpRequestsConfigurer.this.roleHierarchy.get());
return manager;
return access(this.authorizationManagerFactory.hasAnyAuthority(authorities));
}
/**
@ -301,7 +323,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
* customizations
*/
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
*/
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
*/
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
*/
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;
import java.util.Set;
import java.util.function.Supplier;
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.Configuration;
import org.springframework.context.event.EventListener;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.authentication.RememberMeAuthenticationToken;
import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
import org.springframework.security.authorization.AuthorityAuthorizationManager;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationManagerFactory;
import org.springframework.security.authorization.AuthorizationObservationContext;
import org.springframework.security.authorization.SingleResultAuthorizationManager;
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
import org.springframework.security.authorization.event.AuthorizationDeniedEvent;
import org.springframework.security.config.ObjectPostProcessor;
@ -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.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
@ -170,6 +180,26 @@ public class AuthorizeHttpRequestsConfigurerTests {
.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
public void configureWhenObjectPostProcessorRegisteredThenInvokedOnAuthorizationManagerAndAuthorizationFilter() {
this.spring.register(ObjectPostProcessorConfig.class).autowire();
@ -538,6 +568,205 @@ public class AuthorizeHttpRequestsConfigurerTests {
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
public void getWhenExpressionHasIpAddressLocalhostConfiguredIpAddressIsLocalhostThenRespondsWithOk()
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
public void getWhenFullyAuthenticatedConfiguredAndRememberMeTokenThenRespondsWithUnauthorized() throws Exception {
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
@EnableWebSecurity
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
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.security.access.PermissionEvaluator;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.authorization.AuthorizationManagerFactory;
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
@ -38,6 +40,7 @@ import org.springframework.util.Assert;
*
* @author Luke Taylor
* @author Evgeniy Cheban
* @author Steve Riesenberg
* @since 3.1
*/
public abstract class AbstractSecurityExpressionHandler<T>
@ -49,6 +52,8 @@ public abstract class AbstractSecurityExpressionHandler<T>
private @Nullable RoleHierarchy roleHierarchy;
private AuthorizationManagerFactory<T> authorizationManagerFactory = new DefaultAuthorizationManagerFactory<>();
private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator();
@Override
@ -106,11 +111,58 @@ public abstract class AbstractSecurityExpressionHandler<T>
protected abstract SecurityExpressionOperations createSecurityExpressionRoot(
@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() {
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;
}

View File

@ -17,8 +17,6 @@
package org.springframework.security.access.expression;
import java.io.Serializable;
import java.util.Collection;
import java.util.Set;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
@ -26,10 +24,11 @@ import org.jspecify.annotations.Nullable;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationManagerFactory;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.util.Assert;
import org.springframework.util.function.SingletonSupplier;
@ -38,20 +37,19 @@ import org.springframework.util.function.SingletonSupplier;
*
* @author Luke Taylor
* @author Evgeniy Cheban
* @author Steve Riesenberg
* @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 AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
private @Nullable RoleHierarchy roleHierarchy;
private @Nullable Set<String> roles;
private String defaultRolePrefix = "ROLE_";
private final T object;
private AuthorizationManagerFactory<T> authorizationManagerFactory = new DefaultAuthorizationManagerFactory<>();
/**
* Allows "permitAll" expression
*/
@ -77,9 +75,12 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
/**
* Creates a new instance
* @param authentication the {@link Authentication} to use. Cannot be null.
* @deprecated Use {@link #SecurityExpressionRoot(Supplier, Object)} instead
*/
@Deprecated(since = "7.0")
@SuppressWarnings("NullAway")
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.
* Cannot be null.
* @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(() -> {
Authentication value = authentication.get();
Assert.notNull(value, "Authentication object cannot be null");
return value;
});
this.object = object;
}
@Override
public final boolean hasAuthority(String authority) {
return hasAnyAuthority(authority);
return isGranted(this.authorizationManagerFactory.hasAnyAuthority(authority));
}
@Override
public final boolean hasAnyAuthority(String... authorities) {
return hasAnyAuthorityName(null, authorities);
return isGranted(this.authorizationManagerFactory.hasAnyAuthority(authorities));
}
@Override
public final boolean hasRole(String role) {
return hasAnyRole(role);
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
public final boolean hasAnyRole(String... roles) {
return hasAnyAuthorityName(this.defaultRolePrefix, roles);
}
private boolean hasAnyAuthorityName(@Nullable String prefix, String... roles) {
Set<String> roleSet = getAuthoritySet();
for (String role : roles) {
String defaultedRole = getRoleWithDefaultPrefix(prefix, role);
if (roleSet.contains(defaultedRole)) {
return true;
if (this.authorizationManagerFactory instanceof DefaultAuthorizationManagerFactory<T>) {
// To provide passivity for old behavior where hasRole('ROLE_A') is allowed,
// we strip the role prefix when found.
// TODO: Remove in favor of fixing inconsistent behavior?
String rolePrefix = this.defaultRolePrefix;
for (int index = 0; index < roles.length; index++) {
String role = roles[index];
if (role.startsWith(rolePrefix)) {
roles[index] = role.substring(rolePrefix.length());
}
}
}
return false;
return isGranted(this.authorizationManagerFactory.hasAnyRole(roles));
}
@Override
@ -135,33 +162,37 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
@Override
public final boolean permitAll() {
return true;
return isGranted(this.authorizationManagerFactory.permitAll());
}
@Override
public final boolean denyAll() {
return false;
return isGranted(this.authorizationManagerFactory.denyAll());
}
@Override
public final boolean isAnonymous() {
return this.trustResolver.isAnonymous(getAuthentication());
return isGranted(this.authorizationManagerFactory.anonymous());
}
@Override
public final boolean isAuthenticated() {
return this.trustResolver.isAuthenticated(getAuthentication());
return isGranted(this.authorizationManagerFactory.authenticated());
}
@Override
public final boolean isRememberMe() {
return this.trustResolver.isRememberMe(getAuthentication());
return isGranted(this.authorizationManagerFactory.rememberMe());
}
@Override
public final boolean isFullyAuthenticated() {
Authentication authentication = getAuthentication();
return this.trustResolver.isFullyAuthenticated(authentication);
return isGranted(this.authorizationManagerFactory.fullyAuthenticated());
}
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();
}
/**
* @deprecated Use
* {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
*/
@Deprecated(since = "7.0")
public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
this.trustResolver = trustResolver;
getDefaultAuthorizationManagerFactory().setTrustResolver(trustResolver);
}
/**
* @deprecated Use
* {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
*/
@Deprecated(since = "7.0")
public void setRoleHierarchy(@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.
* </p>
* @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;
}
private Set<String> getAuthoritySet() {
if (this.roles == null) {
Collection<? extends GrantedAuthority> userAuthorities = getAuthentication().getAuthorities();
if (this.roleHierarchy != null) {
userAuthorities = this.roleHierarchy.getReachableGrantedAuthorities(userAuthorities);
}
this.roles = AuthorityUtils.authorityListToSet(userAuthorities);
/**
* Sets the {@link AuthorizationManagerFactory} to use for creating instances of
* {@link AuthorizationManager}.
* @param authorizationManagerFactory the {@link AuthorizationManagerFactory} to use
* @since 7.0
*/
public void setAuthorizationManagerFactory(AuthorizationManagerFactory<T> authorizationManagerFactory) {
Assert.notNull(authorizationManagerFactory, "authorizationManagerFactory cannot be null");
this.authorizationManagerFactory = authorizationManagerFactory;
}
/**
* Allows accessing the {@link DefaultAuthorizationManagerFactory} for getting and
* setting defaults. This method will be removed in Spring Security 8.
* @return the {@link DefaultAuthorizationManagerFactory}
* @throws IllegalStateException if a different {@link AuthorizationManagerFactory}
* was already set
* @deprecated Use
* {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
*/
@Deprecated(since = "7.0", forRemoval = true)
private DefaultAuthorizationManagerFactory<T> getDefaultAuthorizationManagerFactory() {
if (!(this.authorizationManagerFactory instanceof DefaultAuthorizationManagerFactory<T> defaultAuthorizationManagerFactory)) {
throw new IllegalStateException(
"authorizationManagerFactory must be an instance of DefaultAuthorizationManagerFactory");
}
return this.roles;
return defaultAuthorizationManagerFactory;
}
@Override
@ -225,24 +294,4 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
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.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.parameters.DefaultSecurityParameterNameDiscoverer;
import org.springframework.util.Assert;
@ -56,11 +57,14 @@ import org.springframework.util.Assert;
* @author Luke Taylor
* @author Evgeniy Cheban
* @author Blagoja Stamatovski
* @author Steve Riesenberg
* @since 3.0
*/
public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpressionHandler<MethodInvocation>
implements MethodSecurityExpressionHandler {
private static final String DEFAULT_ROLE_PREFIX = "ROLE_";
protected final Log logger = LogFactory.getLog(getClass());
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
@ -69,7 +73,7 @@ public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpr
private @Nullable PermissionCacheOptimizer permissionCacheOptimizer = null;
private String defaultRolePrefix = "ROLE_";
private String defaultRolePrefix = DEFAULT_ROLE_PREFIX;
public DefaultMethodSecurityExpressionHandler() {
}
@ -106,12 +110,14 @@ public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpr
private MethodSecurityExpressionOperations createSecurityExpressionRoot(
Supplier<? extends @Nullable Authentication> authentication, MethodInvocation invocation) {
MethodSecurityExpressionRoot root = new MethodSecurityExpressionRoot(authentication);
MethodSecurityExpressionRoot root = new MethodSecurityExpressionRoot(authentication, invocation);
root.setThis(invocation.getThis());
root.setAuthorizationManagerFactory(getAuthorizationManagerFactory());
root.setPermissionEvaluator(getPermissionEvaluator());
root.setTrustResolver(getTrustResolver());
Optional.ofNullable(getRoleHierarchy()).ifPresent(root::setRoleHierarchy);
root.setDefaultRolePrefix(getDefaultRolePrefix());
if (!DEFAULT_ROLE_PREFIX.equals(this.defaultRolePrefix)) {
// Ensure SecurityExpressionRoot can strip the custom role prefix
root.setDefaultRolePrefix(getDefaultRolePrefix());
}
return root;
}
@ -232,15 +238,22 @@ public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpr
* {@link AuthenticationTrustResolverImpl}.
* @param trustResolver the {@link AuthenticationTrustResolver} to use. Cannot be
* null.
* @deprecated Use
* {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
*/
@Deprecated(since = "7.0")
public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
Assert.notNull(trustResolver, "trustResolver cannot be null");
getDefaultAuthorizationManagerFactory().setTrustResolver(trustResolver);
this.trustResolver = trustResolver;
}
/**
* @return The current {@link AuthenticationTrustResolver}
* @deprecated Use
* {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
*/
@Deprecated(since = "7.0")
protected AuthenticationTrustResolver getTrustResolver() {
return this.trustResolver;
}
@ -289,14 +302,24 @@ public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpr
* If null or empty, then no default role prefix is used.
* </p>
* @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;
}
/**
* @return The default role prefix
* @deprecated Use
* {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
*/
@Deprecated(since = "7.0")
protected String getDefaultRolePrefix() {
return this.defaultRolePrefix;
}

View File

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

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;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -53,7 +54,7 @@ public class MethodSecurityExpressionRootTests {
@BeforeEach
public void createContext() {
this.user = mock(Authentication.class);
this.root = new MethodSecurityExpressionRoot(this.user);
this.root = new MethodSecurityExpressionRoot(() -> this.user, mock(MethodInvocation.class));
this.ctx = new StandardEvaluationContext();
this.ctx.setRootObject(this.root);
this.trustResolver = mock(AuthenticationTrustResolver.class);

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.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.authorization.AuthorizationManagerFactory;
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
@ -92,18 +94,18 @@ import org.springframework.util.Assert;
*/
public class SecurityEvaluationContextExtension implements EvaluationContextExtension {
private static final String DEFAULT_ROLE_PREFIX = "ROLE_";
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
.getContextHolderStrategy();
private @Nullable Authentication authentication;
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
private RoleHierarchy roleHierarchy = new NullRoleHierarchy();
private AuthorizationManagerFactory<Object> authorizationManagerFactory = new DefaultAuthorizationManagerFactory<>();
private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator();
private String defaultRolePrefix = "ROLE_";
private String defaultRolePrefix = DEFAULT_ROLE_PREFIX;
/**
* Creates a new instance that uses the current {@link Authentication} found on the
@ -126,14 +128,16 @@ public class SecurityEvaluationContextExtension implements EvaluationContextExte
}
@Override
public SecurityExpressionRoot getRootObject() {
public SecurityExpressionRoot<Object> getRootObject() {
Authentication authentication = getAuthentication();
SecurityExpressionRoot root = new SecurityExpressionRoot(authentication) {
SecurityExpressionRoot<Object> root = new SecurityExpressionRoot<>(() -> authentication, new Object()) {
};
root.setTrustResolver(this.trustResolver);
root.setRoleHierarchy(this.roleHierarchy);
root.setAuthorizationManagerFactory(this.authorizationManagerFactory);
root.setPermissionEvaluator(this.permissionEvaluator);
root.setDefaultRolePrefix(this.defaultRolePrefix);
if (!DEFAULT_ROLE_PREFIX.equals(this.defaultRolePrefix)) {
// Ensure SecurityExpressionRoot can strip the custom role prefix
root.setDefaultRolePrefix(this.defaultRolePrefix);
}
return root;
}
@ -156,15 +160,46 @@ public class SecurityEvaluationContextExtension implements EvaluationContextExte
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
* {@link AuthenticationTrustResolverImpl}. Cannot be null.
* @param trustResolver the {@link AuthenticationTrustResolver} to use
* @since 5.8
* @deprecated Use
* {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
*/
@Deprecated(since = "7.0")
public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
Assert.notNull(trustResolver, "trustResolver cannot be null");
this.trustResolver = trustResolver;
getDefaultAuthorizationManagerFactory().setTrustResolver(trustResolver);
}
/**
@ -172,10 +207,13 @@ public class SecurityEvaluationContextExtension implements EvaluationContextExte
* Cannot be null.
* @param roleHierarchy the {@link RoleHierarchy} to use
* @since 5.8
* @deprecated Use
* {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
*/
@Deprecated(since = "7.0")
public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
Assert.notNull(roleHierarchy, "roleHierarchy cannot be null");
this.roleHierarchy = roleHierarchy;
getDefaultAuthorizationManagerFactory().setRoleHierarchy(roleHierarchy);
}
/**
@ -199,8 +237,12 @@ public class SecurityEvaluationContextExtension implements EvaluationContextExte
* @param defaultRolePrefix the default prefix to add to roles. The default is
* "ROLE_".
* @since 5.8
* @deprecated Use
* {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
*/
@Deprecated(since = "7.0")
public void setDefaultRolePrefix(String defaultRolePrefix) {
getDefaultAuthorizationManagerFactory().setRolePrefix(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.expression.DenyAllPermissionEvaluator;
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.RoleHierarchyImpl;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
@ -102,9 +101,11 @@ public class SecurityEvaluationContextExtensionTests {
public void setTrustResolverWhenNotNullThenVerifyRootObject() {
TestingAuthenticationToken explicit = new TestingAuthenticationToken("explicit", "password", "ROLE_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);
assertThat(getRoot()).extracting("trustResolver").isEqualTo(trustResolver);
assertThat(getRoot().isAuthenticated()).isTrue();
verify(trustResolver).isAuthenticated(explicit);
}
@Test
@ -117,11 +118,11 @@ public class SecurityEvaluationContextExtensionTests {
@Test
public void setRoleHierarchyWhenNotNullThenVerifyRootObject() {
TestingAuthenticationToken explicit = new TestingAuthenticationToken("explicit", "password", "ROLE_EXPLICIT");
TestingAuthenticationToken explicit = new TestingAuthenticationToken("explicit", "password", "ROLE_PARENT");
this.securityExtension = new SecurityEvaluationContextExtension(explicit);
RoleHierarchy roleHierarchy = new NullRoleHierarchy();
RoleHierarchy roleHierarchy = RoleHierarchyImpl.fromHierarchy("ROLE_PARENT > ROLE_EXPLICIT");
this.securityExtension.setRoleHierarchy(roleHierarchy);
assertThat(getRoot()).extracting("roleHierarchy").isEqualTo(roleHierarchy);
assertThat(getRoot().hasRole("EXPLICIT")).isTrue();
}
@Test
@ -143,25 +144,25 @@ public class SecurityEvaluationContextExtensionTests {
@Test
public void setDefaultRolePrefixWhenCustomThenVerifyRootObject() {
TestingAuthenticationToken explicit = new TestingAuthenticationToken("explicit", "password", "ROLE_EXPLICIT");
TestingAuthenticationToken explicit = new TestingAuthenticationToken("explicit", "password", "CUSTOM_EXPLICIT");
this.securityExtension = new SecurityEvaluationContextExtension(explicit);
String defaultRolePrefix = "CUSTOM_";
this.securityExtension.setDefaultRolePrefix(defaultRolePrefix);
assertThat(getRoot()).extracting("defaultRolePrefix").isEqualTo(defaultRolePrefix);
assertThat(getRoot().hasRole("EXPLICIT")).isTrue();
}
@Test
public void getRootObjectWhenAdditionalFieldsNotSetThenVerifyDefaults() {
TestingAuthenticationToken explicit = new TestingAuthenticationToken("explicit", "password", "ROLE_EXPLICIT");
this.securityExtension = new SecurityEvaluationContextExtension(explicit);
SecurityExpressionRoot root = getRoot();
assertThat(root).extracting("trustResolver").isInstanceOf(AuthenticationTrustResolverImpl.class);
assertThat(root).extracting("roleHierarchy").isInstanceOf(NullRoleHierarchy.class);
assertThat(root).extracting("permissionEvaluator").isInstanceOf(DenyAllPermissionEvaluator.class);
assertThat(root).extracting("defaultRolePrefix").isEqualTo("ROLE_");
SecurityExpressionRoot<?> securityExpressionRoot = getRoot();
assertThat(securityExpressionRoot.isAuthenticated()).isTrue();
assertThat(securityExpressionRoot.hasRole("PARENT")).isFalse();
assertThat(securityExpressionRoot.hasRole("EXPLICIT")).isTrue();
assertThat(securityExpressionRoot.hasPermission(new Object(), "read")).isFalse();
}
private SecurityExpressionRoot getRoot() {
private SecurityExpressionRoot<?> getRoot() {
return this.securityExtension.getRootObject();
}

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.
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]]
==== AuthorizationManagers
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 <<authorize-requests, authorize a request programmatically>>
* 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]]
== 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.
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]]
== 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.
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]]
== Authorizing Arbitrary Objects

View File

@ -12,6 +12,7 @@ Each section that follows will indicate the more notable removals as well as the
== Core
* 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

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="DefaultLoginPageGeneratingFilterTests\.java" checks="SpringLeadingWhitespace"/>
<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 -->
<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.SecurityExpressionOperations;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.authorization.AuthorizationManagerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
/**
* 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>> {
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
@Override
public EvaluationContext createEvaluationContext(Supplier<? extends @Nullable Authentication> authentication,
Message<T> message) {
MessageSecurityExpressionRoot root = createSecurityExpressionRoot(authentication, message);
MessageSecurityExpressionRoot<T> root = createSecurityExpressionRoot(authentication, message);
StandardEvaluationContext ctx = new StandardEvaluationContext(root);
BeanResolver beanResolver = getBeanResolver();
if (beanResolver != null) {
@ -64,18 +61,21 @@ public class DefaultMessageSecurityExpressionHandler<T> extends AbstractSecurity
return createSecurityExpressionRoot(() -> authentication, invocation);
}
private MessageSecurityExpressionRoot createSecurityExpressionRoot(
private MessageSecurityExpressionRoot<T> createSecurityExpressionRoot(
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.setTrustResolver(this.trustResolver);
root.setRoleHierarchy(getRoleHierarchy());
return root;
}
/**
* @deprecated Use
* {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
*/
@Deprecated(since = "7.0")
public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
Assert.notNull(trustResolver, "trustResolver cannot be null");
this.trustResolver = trustResolver;
getDefaultAuthorizationManagerFactory().setTrustResolver(trustResolver);
}
}

View File

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

View File

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

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.core.Authentication;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
import org.springframework.security.web.util.matcher.IpAddressMatcher;
/**
* @author Luke Taylor
* @author Evgeniy Cheban
* @author Steve Riesenberg
* @since 3.0
*/
public class WebSecurityExpressionRoot extends SecurityExpressionRoot {
public class WebSecurityExpressionRoot extends SecurityExpressionRoot<RequestAuthorizationContext> {
/**
* Allows direct access to the request object
*/
public final HttpServletRequest request;
/**
* @deprecated Use
* {@link #WebSecurityExpressionRoot(Supplier, RequestAuthorizationContext)} instead
*/
@Deprecated(since = "7.0")
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 request the {@link HttpServletRequest} to use
* @since 5.8
* @deprecated Use
* {@link #WebSecurityExpressionRoot(Supplier, RequestAuthorizationContext)} instead
*/
@Deprecated(since = "7.0")
@SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1246
public WebSecurityExpressionRoot(Supplier<? extends @Nullable Authentication> authentication,
HttpServletRequest request) {
super(authentication);
super(authentication, new RequestAuthorizationContext(request));
this.request = request;
}
/**
* Creates an instance for the given {@link Supplier} of the {@link Authentication}
* and {@link HttpServletRequest}.
* @param authentication the {@link Supplier} of the {@link Authentication} to use
* @param context the {@link RequestAuthorizationContext} to use
* @since 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
* 202.24.0.0/14).