diff --git a/config/spring-security-config.gradle b/config/spring-security-config.gradle
index d641eda0ab3..0bda161bd49 100644
--- a/config/spring-security-config.gradle
+++ b/config/spring-security-config.gradle
@@ -32,6 +32,7 @@ dependencies {
optional'org.springframework:spring-websocket'
optional 'org.jetbrains.kotlin:kotlin-reflect'
optional 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
+ optional 'javax.annotation:jsr250-api'
provided 'javax.servlet:javax.servlet-api'
diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/EnableMethodSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/EnableMethodSecurity.java
new file mode 100644
index 00000000000..c64a4355be7
--- /dev/null
+++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/EnableMethodSecurity.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2002-2021 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.config.annotation.method.configuration;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.context.annotation.AdviceMode;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.core.Ordered;
+import org.springframework.security.access.annotation.Secured;
+
+/**
+ * Enables Spring Security Method Security.
+ * @author Evgeniy Cheban
+ * @since 5.5
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Documented
+@Import(MethodSecuritySelector.class)
+@Configuration
+public @interface EnableMethodSecurity {
+
+ /**
+ * Determines if Spring Security's {@link Secured} annotation should be enabled.
+ * Default is false.
+ * @return true if {@link Secured} annotation should be enabled false otherwise
+ */
+ boolean securedEnabled() default false;
+
+ /**
+ * Determines if JSR-250 annotations should be enabled. Default is false.
+ * @return true if JSR-250 should be enabled false otherwise
+ */
+ boolean jsr250Enabled() default false;
+
+ /**
+ * Indicate whether subclass-based (CGLIB) proxies are to be created as opposed to
+ * standard Java interface-based proxies. The default is {@code false}.
+ * Applicable only if {@link #mode()} is set to {@link AdviceMode#PROXY}.
+ *
+ * Note that setting this attribute to {@code true} will affect all
+ * Spring-managed beans requiring proxying, not just those marked with
+ * {@code @Cacheable}. For example, other beans marked with Spring's
+ * {@code @Transactional} annotation will be upgraded to subclass proxying at the same
+ * time. This approach has no negative impact in practice unless one is explicitly
+ * expecting one type of proxy vs another, e.g. in tests.
+ * @return true if subclass-based (CGLIB) proxies are to be created
+ */
+ boolean proxyTargetClass() default false;
+
+ /**
+ * Indicate how security advice should be applied. The default is
+ * {@link AdviceMode#PROXY}.
+ * @see AdviceMode
+ * @return the {@link AdviceMode} to use
+ */
+ AdviceMode mode() default AdviceMode.PROXY;
+
+ /**
+ * Indicate the ordering of the execution of the security advisor when multiple
+ * advices are applied at a specific joinpoint. The default is
+ * {@link Ordered#LOWEST_PRECEDENCE}.
+ * @return the order the security advisor should be applied
+ */
+ int order() default Ordered.LOWEST_PRECEDENCE;
+
+}
diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityConfiguration.java
new file mode 100644
index 00000000000..529a78f45c4
--- /dev/null
+++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityConfiguration.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2002-2021 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.config.annotation.method.configuration;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.security.DenyAll;
+import javax.annotation.security.PermitAll;
+import javax.annotation.security.RolesAllowed;
+
+import org.springframework.aop.MethodMatcher;
+import org.springframework.aop.Pointcut;
+import org.springframework.aop.support.AopUtils;
+import org.springframework.aop.support.DefaultPointcutAdvisor;
+import org.springframework.aop.support.Pointcuts;
+import org.springframework.aop.support.StaticMethodMatcher;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.ImportAware;
+import org.springframework.context.annotation.Role;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.security.access.annotation.Jsr250AuthorizationManager;
+import org.springframework.security.access.annotation.Secured;
+import org.springframework.security.access.annotation.SecuredAuthorizationManager;
+import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
+import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
+import org.springframework.security.access.intercept.aopalliance.AuthorizationMethodInterceptor;
+import org.springframework.security.access.method.AuthorizationManagerMethodAfterAdvice;
+import org.springframework.security.access.method.AuthorizationManagerMethodBeforeAdvice;
+import org.springframework.security.access.method.AuthorizationMethodAfterAdvice;
+import org.springframework.security.access.method.AuthorizationMethodBeforeAdvice;
+import org.springframework.security.access.method.DelegatingAuthorizationMethodAfterAdvice;
+import org.springframework.security.access.method.DelegatingAuthorizationMethodBeforeAdvice;
+import org.springframework.security.access.method.MethodAuthorizationContext;
+import org.springframework.security.access.prepost.PostAuthorize;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager;
+import org.springframework.security.authorization.method.PostFilterAuthorizationMethodAfterAdvice;
+import org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager;
+import org.springframework.security.authorization.method.PreFilterAuthorizationMethodBeforeAdvice;
+import org.springframework.security.config.core.GrantedAuthorityDefaults;
+
+/**
+ * Base {@link Configuration} for enabling Spring Security Method Security.
+ *
+ * @author Evgeniy Cheban
+ * @see EnableMethodSecurity
+ * @since 5.5
+ */
+@Configuration(proxyBeanMethods = false)
+@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+final class MethodSecurityConfiguration implements ImportAware {
+
+ private MethodSecurityExpressionHandler methodSecurityExpressionHandler;
+
+ private GrantedAuthorityDefaults grantedAuthorityDefaults;
+
+ private AuthorizationMethodBeforeAdvice authorizationMethodBeforeAdvice;
+
+ private AuthorizationMethodAfterAdvice authorizationMethodAfterAdvice;
+
+ private AnnotationAttributes enableMethodSecurity;
+
+ @Bean
+ @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+ DefaultPointcutAdvisor methodSecurityAdvisor(AuthorizationMethodInterceptor interceptor) {
+ Pointcut pointcut = Pointcuts.union(getAuthorizationMethodBeforeAdvice(), getAuthorizationMethodAfterAdvice());
+ DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, interceptor);
+ advisor.setOrder(order());
+ return advisor;
+ }
+
+ @Bean
+ @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+ AuthorizationMethodInterceptor authorizationMethodInterceptor() {
+ return new AuthorizationMethodInterceptor(getAuthorizationMethodBeforeAdvice(),
+ getAuthorizationMethodAfterAdvice());
+ }
+
+ private MethodSecurityExpressionHandler getMethodSecurityExpressionHandler() {
+ if (this.methodSecurityExpressionHandler == null) {
+ this.methodSecurityExpressionHandler = new DefaultMethodSecurityExpressionHandler();
+ }
+ return this.methodSecurityExpressionHandler;
+ }
+
+ @Autowired(required = false)
+ void setMethodSecurityExpressionHandler(MethodSecurityExpressionHandler methodSecurityExpressionHandler) {
+ this.methodSecurityExpressionHandler = methodSecurityExpressionHandler;
+ }
+
+ @Autowired(required = false)
+ void setGrantedAuthorityDefaults(GrantedAuthorityDefaults grantedAuthorityDefaults) {
+ this.grantedAuthorityDefaults = grantedAuthorityDefaults;
+ }
+
+ private AuthorizationMethodBeforeAdvice getAuthorizationMethodBeforeAdvice() {
+ if (this.authorizationMethodBeforeAdvice == null) {
+ this.authorizationMethodBeforeAdvice = createDefaultAuthorizationMethodBeforeAdvice();
+ }
+ return this.authorizationMethodBeforeAdvice;
+ }
+
+ private AuthorizationMethodBeforeAdvice createDefaultAuthorizationMethodBeforeAdvice() {
+ List> beforeAdvices = new ArrayList<>();
+ beforeAdvices.add(getPreFilterAuthorizationMethodBeforeAdvice());
+ beforeAdvices.add(getPreAuthorizeAuthorizationMethodBeforeAdvice());
+ if (securedEnabled()) {
+ beforeAdvices.add(getSecuredAuthorizationMethodBeforeAdvice());
+ }
+ if (jsr250Enabled()) {
+ beforeAdvices.add(getJsr250AuthorizationMethodBeforeAdvice());
+ }
+ return new DelegatingAuthorizationMethodBeforeAdvice(beforeAdvices);
+ }
+
+ private PreFilterAuthorizationMethodBeforeAdvice getPreFilterAuthorizationMethodBeforeAdvice() {
+ PreFilterAuthorizationMethodBeforeAdvice preFilterBeforeAdvice = new PreFilterAuthorizationMethodBeforeAdvice();
+ preFilterBeforeAdvice.setExpressionHandler(getMethodSecurityExpressionHandler());
+ return preFilterBeforeAdvice;
+ }
+
+ private AuthorizationMethodBeforeAdvice getPreAuthorizeAuthorizationMethodBeforeAdvice() {
+ MethodMatcher methodMatcher = new SecurityAnnotationsStaticMethodMatcher(PreAuthorize.class);
+ PreAuthorizeAuthorizationManager authorizationManager = new PreAuthorizeAuthorizationManager();
+ authorizationManager.setExpressionHandler(getMethodSecurityExpressionHandler());
+ return new AuthorizationManagerMethodBeforeAdvice<>(methodMatcher, authorizationManager);
+ }
+
+ private AuthorizationManagerMethodBeforeAdvice getSecuredAuthorizationMethodBeforeAdvice() {
+ MethodMatcher methodMatcher = new SecurityAnnotationsStaticMethodMatcher(Secured.class);
+ SecuredAuthorizationManager authorizationManager = new SecuredAuthorizationManager();
+ return new AuthorizationManagerMethodBeforeAdvice<>(methodMatcher, authorizationManager);
+ }
+
+ private AuthorizationManagerMethodBeforeAdvice getJsr250AuthorizationMethodBeforeAdvice() {
+ MethodMatcher methodMatcher = new SecurityAnnotationsStaticMethodMatcher(DenyAll.class, PermitAll.class,
+ RolesAllowed.class);
+ Jsr250AuthorizationManager authorizationManager = new Jsr250AuthorizationManager();
+ if (this.grantedAuthorityDefaults != null) {
+ authorizationManager.setRolePrefix(this.grantedAuthorityDefaults.getRolePrefix());
+ }
+ return new AuthorizationManagerMethodBeforeAdvice<>(methodMatcher, authorizationManager);
+ }
+
+ @Autowired(required = false)
+ void setAuthorizationMethodBeforeAdvice(
+ AuthorizationMethodBeforeAdvice authorizationMethodBeforeAdvice) {
+ this.authorizationMethodBeforeAdvice = authorizationMethodBeforeAdvice;
+ }
+
+ private AuthorizationMethodAfterAdvice getAuthorizationMethodAfterAdvice() {
+ if (this.authorizationMethodAfterAdvice == null) {
+ this.authorizationMethodAfterAdvice = createDefaultAuthorizationMethodAfterAdvice();
+ }
+ return this.authorizationMethodAfterAdvice;
+ }
+
+ private AuthorizationMethodAfterAdvice createDefaultAuthorizationMethodAfterAdvice() {
+ List> afterAdvices = new ArrayList<>();
+ afterAdvices.add(getPostFilterAuthorizationMethodAfterAdvice());
+ afterAdvices.add(getPostAuthorizeAuthorizationMethodAfterAdvice());
+ return new DelegatingAuthorizationMethodAfterAdvice(afterAdvices);
+ }
+
+ private PostFilterAuthorizationMethodAfterAdvice getPostFilterAuthorizationMethodAfterAdvice() {
+ PostFilterAuthorizationMethodAfterAdvice postFilterAfterAdvice = new PostFilterAuthorizationMethodAfterAdvice();
+ postFilterAfterAdvice.setExpressionHandler(getMethodSecurityExpressionHandler());
+ return postFilterAfterAdvice;
+ }
+
+ private AuthorizationManagerMethodAfterAdvice getPostAuthorizeAuthorizationMethodAfterAdvice() {
+ MethodMatcher methodMatcher = new SecurityAnnotationsStaticMethodMatcher(PostAuthorize.class);
+ PostAuthorizeAuthorizationManager authorizationManager = new PostAuthorizeAuthorizationManager();
+ authorizationManager.setExpressionHandler(getMethodSecurityExpressionHandler());
+ return new AuthorizationManagerMethodAfterAdvice<>(methodMatcher, authorizationManager);
+ }
+
+ @Autowired(required = false)
+ void setAuthorizationMethodAfterAdvice(
+ AuthorizationMethodAfterAdvice authorizationMethodAfterAdvice) {
+ this.authorizationMethodAfterAdvice = authorizationMethodAfterAdvice;
+ }
+
+ @Override
+ public void setImportMetadata(AnnotationMetadata importMetadata) {
+ Map attributes = importMetadata.getAnnotationAttributes(EnableMethodSecurity.class.getName());
+ this.enableMethodSecurity = AnnotationAttributes.fromMap(attributes);
+ }
+
+ private boolean securedEnabled() {
+ return this.enableMethodSecurity.getBoolean("securedEnabled");
+ }
+
+ private boolean jsr250Enabled() {
+ return this.enableMethodSecurity.getBoolean("jsr250Enabled");
+ }
+
+ private int order() {
+ return this.enableMethodSecurity.getNumber("order");
+ }
+
+ private static final class SecurityAnnotationsStaticMethodMatcher extends StaticMethodMatcher {
+
+ private final Set> annotationClasses;
+
+ @SafeVarargs
+ private SecurityAnnotationsStaticMethodMatcher(Class extends Annotation>... annotationClasses) {
+ this.annotationClasses = new HashSet<>(Arrays.asList(annotationClasses));
+ }
+
+ @Override
+ public boolean matches(Method method, Class> targetClass) {
+ Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
+ return hasAnnotations(specificMethod) || hasAnnotations(specificMethod.getDeclaringClass());
+ }
+
+ private boolean hasAnnotations(AnnotatedElement annotatedElement) {
+ Set annotations = AnnotatedElementUtils.findAllMergedAnnotations(annotatedElement,
+ this.annotationClasses);
+ return !annotations.isEmpty();
+ }
+
+ }
+
+}
diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecuritySelector.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecuritySelector.java
new file mode 100644
index 00000000000..ed0df62454a
--- /dev/null
+++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecuritySelector.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2002-2021 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.config.annotation.method.configuration;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.context.annotation.AdviceMode;
+import org.springframework.context.annotation.AdviceModeImportSelector;
+import org.springframework.context.annotation.AutoProxyRegistrar;
+
+/**
+ * Dynamically determines which imports to include using the {@link EnableMethodSecurity}
+ * annotation.
+ *
+ * @author Evgeniy Cheban
+ * @since 5.5
+ */
+final class MethodSecuritySelector extends AdviceModeImportSelector {
+
+ @Override
+ protected String[] selectImports(AdviceMode adviceMode) {
+ if (adviceMode == AdviceMode.PROXY) {
+ return getProxyImports();
+ }
+ throw new IllegalStateException("AdviceMode '" + adviceMode + "' is not supported");
+ }
+
+ private String[] getProxyImports() {
+ List result = new ArrayList<>();
+ result.add(AutoProxyRegistrar.class.getName());
+ result.add(MethodSecurityConfiguration.class.getName());
+ return result.toArray(new String[0]);
+ }
+
+}
diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityConfigurationTests.java
new file mode 100644
index 00000000000..41bf8f7c3c0
--- /dev/null
+++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityConfigurationTests.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright 2002-2021 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.config.annotation.method.configuration;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.aop.MethodMatcher;
+import org.springframework.aop.support.JdkRegexpMethodPointcut;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.access.PermissionEvaluator;
+import org.springframework.security.access.annotation.BusinessService;
+import org.springframework.security.access.annotation.ExpressionProtectedBusinessServiceImpl;
+import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
+import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
+import org.springframework.security.access.method.AuthorizationManagerMethodBeforeAdvice;
+import org.springframework.security.access.method.AuthorizationMethodAfterAdvice;
+import org.springframework.security.access.method.AuthorizationMethodBeforeAdvice;
+import org.springframework.security.access.method.MethodAuthorizationContext;
+import org.springframework.security.authorization.AuthorizationDecision;
+import org.springframework.security.authorization.AuthorizationManager;
+import org.springframework.security.config.test.SpringTestRule;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
+import org.springframework.security.test.context.support.WithAnonymousUser;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+
+/**
+ * Tests for {@link MethodSecurityConfiguration}.
+ *
+ * @author Evgeniy Cheban
+ */
+@RunWith(SpringRunner.class)
+@SecurityTestExecutionListeners
+public class MethodSecurityConfigurationTests {
+
+ @Rule
+ public final SpringTestRule spring = new SpringTestRule();
+
+ @Autowired(required = false)
+ MethodSecurityService methodSecurityService;
+
+ @Autowired(required = false)
+ BusinessService businessService;
+
+ @WithMockUser(roles = "ADMIN")
+ @Test
+ public void preAuthorizeWhenRoleAdminThenAccessDeniedException() {
+ this.spring.register(MethodSecurityServiceConfig.class).autowire();
+ assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::preAuthorize)
+ .withMessage("Access Denied");
+ }
+
+ @WithAnonymousUser
+ @Test
+ public void preAuthorizePermitAllWhenRoleAnonymousThenPasses() {
+ this.spring.register(MethodSecurityServiceConfig.class).autowire();
+ String result = this.methodSecurityService.preAuthorizePermitAll();
+ assertThat(result).isNull();
+ }
+
+ @WithAnonymousUser
+ @Test
+ public void preAuthorizeNotAnonymousWhenRoleAnonymousThenAccessDeniedException() {
+ this.spring.register(MethodSecurityServiceConfig.class).autowire();
+ assertThatExceptionOfType(AccessDeniedException.class)
+ .isThrownBy(this.methodSecurityService::preAuthorizeNotAnonymous).withMessage("Access Denied");
+ }
+
+ @WithMockUser
+ @Test
+ public void preAuthorizeNotAnonymousWhenRoleUserThenPasses() {
+ this.spring.register(MethodSecurityServiceConfig.class).autowire();
+ this.methodSecurityService.preAuthorizeNotAnonymous();
+ }
+
+ @WithMockUser
+ @Test
+ public void securedWhenRoleUserThenAccessDeniedException() {
+ this.spring.register(MethodSecurityServiceConfig.class).autowire();
+ assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::secured)
+ .withMessage("Access Denied");
+ }
+
+ @WithMockUser(roles = "ADMIN")
+ @Test
+ public void securedWhenRoleAdminThenPasses() {
+ this.spring.register(MethodSecurityServiceConfig.class).autowire();
+ String result = this.methodSecurityService.secured();
+ assertThat(result).isNull();
+ }
+
+ @WithMockUser(roles = "ADMIN")
+ @Test
+ public void securedUserWhenRoleAdminThenAccessDeniedException() {
+ this.spring.register(MethodSecurityServiceConfig.class).autowire();
+ assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::securedUser)
+ .withMessage("Access Denied");
+ }
+
+ @WithMockUser
+ @Test
+ public void securedUserWhenRoleUserThenPasses() {
+ this.spring.register(MethodSecurityServiceConfig.class).autowire();
+ String result = this.methodSecurityService.securedUser();
+ assertThat(result).isNull();
+ }
+
+ @WithMockUser
+ @Test
+ public void preAuthorizeAdminWhenRoleUserThenAccessDeniedException() {
+ this.spring.register(MethodSecurityServiceConfig.class).autowire();
+ assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::preAuthorizeAdmin)
+ .withMessage("Access Denied");
+ }
+
+ @WithMockUser(roles = "ADMIN")
+ @Test
+ public void preAuthorizeAdminWhenRoleAdminThenPasses() {
+ this.spring.register(MethodSecurityServiceConfig.class).autowire();
+ this.methodSecurityService.preAuthorizeAdmin();
+ }
+
+ @WithMockUser
+ @Test
+ public void postHasPermissionWhenParameterIsNotGrantThenAccessDeniedException() {
+ this.spring.register(CustomPermissionEvaluatorConfig.class, MethodSecurityServiceConfig.class).autowire();
+ assertThatExceptionOfType(AccessDeniedException.class)
+ .isThrownBy(() -> this.methodSecurityService.postHasPermission("deny")).withMessage("Access Denied");
+ }
+
+ @WithMockUser
+ @Test
+ public void postHasPermissionWhenParameterIsGrantThenPasses() {
+ this.spring.register(CustomPermissionEvaluatorConfig.class, MethodSecurityServiceConfig.class).autowire();
+ String result = this.methodSecurityService.postHasPermission("grant");
+ assertThat(result).isNull();
+ }
+
+ @WithMockUser
+ @Test
+ public void postAnnotationWhenParameterIsNotGrantThenAccessDeniedException() {
+ this.spring.register(MethodSecurityServiceConfig.class).autowire();
+ assertThatExceptionOfType(AccessDeniedException.class)
+ .isThrownBy(() -> this.methodSecurityService.postAnnotation("deny")).withMessage("Access Denied");
+ }
+
+ @WithMockUser
+ @Test
+ public void postAnnotationWhenParameterIsGrantThenPasses() {
+ this.spring.register(MethodSecurityServiceConfig.class).autowire();
+ String result = this.methodSecurityService.postAnnotation("grant");
+ assertThat(result).isNull();
+ }
+
+ @WithMockUser("bob")
+ @Test
+ public void methodReturningAListWhenPrePostFiltersConfiguredThenFiltersList() {
+ this.spring.register(BusinessServiceConfig.class).autowire();
+ List names = new ArrayList<>();
+ names.add("bob");
+ names.add("joe");
+ names.add("sam");
+ List> result = this.businessService.methodReturningAList(names);
+ assertThat(result).hasSize(1);
+ assertThat(result.get(0)).isEqualTo("bob");
+ }
+
+ @WithMockUser("bob")
+ @Test
+ public void methodReturningAnArrayWhenPostFilterConfiguredThenFiltersArray() {
+ this.spring.register(BusinessServiceConfig.class).autowire();
+ List names = new ArrayList<>();
+ names.add("bob");
+ names.add("joe");
+ names.add("sam");
+ Object[] result = this.businessService.methodReturningAnArray(names.toArray());
+ assertThat(result).hasSize(1);
+ assertThat(result[0]).isEqualTo("bob");
+ }
+
+ @WithMockUser("bob")
+ @Test
+ public void securedUserWhenCustomBeforeAdviceConfiguredAndNameBobThenPasses() {
+ this.spring.register(CustomAuthorizationManagerBeforeAdviceConfig.class, MethodSecurityServiceConfig.class)
+ .autowire();
+ String result = this.methodSecurityService.securedUser();
+ assertThat(result).isNull();
+ }
+
+ @WithMockUser("joe")
+ @Test
+ public void securedUserWhenCustomBeforeAdviceConfiguredAndNameNotBobThenAccessDeniedException() {
+ this.spring.register(CustomAuthorizationManagerBeforeAdviceConfig.class, MethodSecurityServiceConfig.class)
+ .autowire();
+ assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::securedUser)
+ .withMessage("Access Denied");
+ }
+
+ @WithMockUser("bob")
+ @Test
+ public void securedUserWhenCustomAfterAdviceConfiguredAndNameBobThenGranted() {
+ this.spring.register(CustomAuthorizationManagerAfterAdviceConfig.class, MethodSecurityServiceConfig.class)
+ .autowire();
+ String result = this.methodSecurityService.securedUser();
+ assertThat(result).isEqualTo("granted");
+ }
+
+ @WithMockUser("joe")
+ @Test
+ public void securedUserWhenCustomAfterAdviceConfiguredAndNameNotBobThenAccessDeniedException() {
+ this.spring.register(CustomAuthorizationManagerAfterAdviceConfig.class, MethodSecurityServiceConfig.class)
+ .autowire();
+ assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::securedUser)
+ .withMessage("Access Denied for User 'joe'");
+ }
+
+ @WithMockUser(roles = "ADMIN")
+ @Test
+ public void jsr250WhenRoleAdminThenAccessDeniedException() {
+ this.spring.register(MethodSecurityServiceConfig.class).autowire();
+ assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::jsr250)
+ .withMessage("Access Denied");
+ }
+
+ @WithAnonymousUser
+ @Test
+ public void jsr250PermitAllWhenRoleAnonymousThenPasses() {
+ this.spring.register(MethodSecurityServiceConfig.class).autowire();
+ String result = this.methodSecurityService.jsr250PermitAll();
+ assertThat(result).isNull();
+ }
+
+ @WithMockUser(roles = "ADMIN")
+ @Test
+ public void rolesAllowedUserWhenRoleAdminThenAccessDeniedException() {
+ this.spring.register(BusinessServiceConfig.class).autowire();
+ assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.businessService::rolesAllowedUser)
+ .withMessage("Access Denied");
+ }
+
+ @WithMockUser
+ @Test
+ public void rolesAllowedUserWhenRoleUserThenPasses() {
+ this.spring.register(BusinessServiceConfig.class).autowire();
+ this.businessService.rolesAllowedUser();
+ }
+
+ @EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
+ static class MethodSecurityServiceConfig {
+
+ @Bean
+ MethodSecurityService methodSecurityService() {
+ return new MethodSecurityServiceImpl();
+ }
+
+ }
+
+ @EnableMethodSecurity(jsr250Enabled = true)
+ static class BusinessServiceConfig {
+
+ @Bean
+ BusinessService businessService() {
+ return new ExpressionProtectedBusinessServiceImpl();
+ }
+
+ }
+
+ @EnableMethodSecurity
+ static class CustomPermissionEvaluatorConfig {
+
+ @Bean
+ MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
+ DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
+ expressionHandler.setPermissionEvaluator(new PermissionEvaluator() {
+ @Override
+ public boolean hasPermission(Authentication authentication, Object targetDomainObject,
+ Object permission) {
+ return "grant".equals(targetDomainObject);
+ }
+
+ @Override
+ public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType,
+ Object permission) {
+ throw new UnsupportedOperationException();
+ }
+ });
+ return expressionHandler;
+ }
+
+ }
+
+ @EnableMethodSecurity
+ static class CustomAuthorizationManagerBeforeAdviceConfig {
+
+ @Bean
+ AuthorizationMethodBeforeAdvice customBeforeAdvice() {
+ JdkRegexpMethodPointcut methodMatcher = new JdkRegexpMethodPointcut();
+ methodMatcher.setPattern(".*MethodSecurityServiceImpl.*securedUser");
+ AuthorizationManager authorizationManager = (a,
+ o) -> new AuthorizationDecision("bob".equals(a.get().getName()));
+ return new AuthorizationManagerMethodBeforeAdvice<>(methodMatcher, authorizationManager);
+ }
+
+ }
+
+ @EnableMethodSecurity
+ static class CustomAuthorizationManagerAfterAdviceConfig {
+
+ @Bean
+ AuthorizationMethodAfterAdvice customAfterAdvice() {
+ JdkRegexpMethodPointcut methodMatcher = new JdkRegexpMethodPointcut();
+ methodMatcher.setPattern(".*MethodSecurityServiceImpl.*securedUser");
+ return new AuthorizationMethodAfterAdvice() {
+ @Override
+ public MethodMatcher getMethodMatcher() {
+ return methodMatcher;
+ }
+
+ @Override
+ public Object after(Supplier authentication,
+ MethodAuthorizationContext methodAuthorizationContext, Object returnedObject) {
+ Authentication auth = authentication.get();
+ if ("bob".equals(auth.getName())) {
+ return "granted";
+ }
+ throw new AccessDeniedException("Access Denied for User '" + auth.getName() + "'");
+ }
+ };
+ }
+
+ }
+
+}
diff --git a/core/src/main/java/org/springframework/security/access/annotation/AbstractAuthorizationManagerRegistry.java b/core/src/main/java/org/springframework/security/access/annotation/AbstractAuthorizationManagerRegistry.java
new file mode 100644
index 00000000000..855f2ab0c01
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/access/annotation/AbstractAuthorizationManagerRegistry.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2002-2021 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.access.annotation;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.aopalliance.intercept.MethodInvocation;
+
+import org.springframework.core.MethodClassKey;
+import org.springframework.lang.NonNull;
+import org.springframework.security.access.method.MethodAuthorizationContext;
+import org.springframework.security.authorization.AuthorizationManager;
+
+/**
+ * An abstract registry which provides an {@link AuthorizationManager} for the
+ * {@link MethodInvocation}.
+ *
+ * @author Evgeniy Cheban
+ * @since 5.5
+ */
+abstract class AbstractAuthorizationManagerRegistry {
+
+ static final AuthorizationManager NULL_MANAGER = (a, o) -> null;
+
+ private final Map> cachedManagers = new ConcurrentHashMap<>();
+
+ /**
+ * Returns an {@link AuthorizationManager} for the {@link MethodAuthorizationContext}.
+ * @param methodAuthorizationContext the {@link MethodAuthorizationContext} to use
+ * @return an {@link AuthorizationManager} to use
+ */
+ final AuthorizationManager getManager(
+ MethodAuthorizationContext methodAuthorizationContext) {
+ MethodInvocation methodInvocation = methodAuthorizationContext.getMethodInvocation();
+ Method method = methodInvocation.getMethod();
+ Class> targetClass = methodAuthorizationContext.getTargetClass();
+ MethodClassKey cacheKey = new MethodClassKey(method, targetClass);
+ return this.cachedManagers.computeIfAbsent(cacheKey, (k) -> resolveManager(method, targetClass));
+ }
+
+ /**
+ * Subclasses should implement this method to provide the non-null
+ * {@link AuthorizationManager} for the method and the target class.
+ * @param method the method
+ * @param targetClass the target class
+ * @return the non-null {@link AuthorizationManager}
+ */
+ @NonNull
+ abstract AuthorizationManager resolveManager(Method method, Class> targetClass);
+
+}
diff --git a/core/src/main/java/org/springframework/security/access/annotation/Jsr250AuthorizationManager.java b/core/src/main/java/org/springframework/security/access/annotation/Jsr250AuthorizationManager.java
new file mode 100644
index 00000000000..19ed88ee23c
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/access/annotation/Jsr250AuthorizationManager.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2002-2021 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.access.annotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Supplier;
+
+import javax.annotation.security.DenyAll;
+import javax.annotation.security.PermitAll;
+import javax.annotation.security.RolesAllowed;
+
+import org.aopalliance.intercept.MethodInvocation;
+
+import org.springframework.aop.support.AopUtils;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.lang.NonNull;
+import org.springframework.security.access.method.MethodAuthorizationContext;
+import org.springframework.security.authorization.AuthorityAuthorizationManager;
+import org.springframework.security.authorization.AuthorizationDecision;
+import org.springframework.security.authorization.AuthorizationManager;
+import org.springframework.security.core.Authentication;
+import org.springframework.util.Assert;
+
+/**
+ * An {@link AuthorizationManager} which can determine if an {@link Authentication} has
+ * access to the {@link MethodInvocation} by evaluating if the {@link Authentication}
+ * contains a specified authority from the JSR-250 security annotations.
+ *
+ * @author Evgeniy Cheban
+ * @since 5.5
+ */
+public final class Jsr250AuthorizationManager implements AuthorizationManager {
+
+ private static final Set> JSR250_ANNOTATIONS = new HashSet<>();
+
+ static {
+ JSR250_ANNOTATIONS.add(DenyAll.class);
+ JSR250_ANNOTATIONS.add(PermitAll.class);
+ JSR250_ANNOTATIONS.add(RolesAllowed.class);
+ }
+
+ private final Jsr250AuthorizationManagerRegistry registry = new Jsr250AuthorizationManagerRegistry();
+
+ private String rolePrefix = "ROLE_";
+
+ /**
+ * Sets the role prefix. Defaults to "ROLE_".
+ * @param rolePrefix the role prefix to use
+ */
+ public void setRolePrefix(String rolePrefix) {
+ Assert.notNull(rolePrefix, "rolePrefix cannot be null");
+ this.rolePrefix = rolePrefix;
+ }
+
+ /**
+ * Determines if an {@link Authentication} has access to the {@link MethodInvocation}
+ * by evaluating if the {@link Authentication} contains a specified authority from the
+ * JSR-250 security annotations.
+ * @param authentication the {@link Supplier} of the {@link Authentication} to check
+ * @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check
+ * @return an {@link AuthorizationDecision} or null if the JSR-250 security
+ * annotations is not present
+ */
+ @Override
+ public AuthorizationDecision check(Supplier authentication,
+ MethodAuthorizationContext methodAuthorizationContext) {
+ AuthorizationManager delegate = this.registry
+ .getManager(methodAuthorizationContext);
+ return delegate.check(authentication, methodAuthorizationContext);
+ }
+
+ private final class Jsr250AuthorizationManagerRegistry extends AbstractAuthorizationManagerRegistry {
+
+ @NonNull
+ @Override
+ AuthorizationManager resolveManager(Method method, Class> targetClass) {
+ for (Annotation annotation : findJsr250Annotations(method, targetClass)) {
+ if (annotation instanceof DenyAll) {
+ return (a, o) -> new AuthorizationDecision(false);
+ }
+ if (annotation instanceof PermitAll) {
+ return (a, o) -> new AuthorizationDecision(true);
+ }
+ if (annotation instanceof RolesAllowed) {
+ RolesAllowed rolesAllowed = (RolesAllowed) annotation;
+ return AuthorityAuthorizationManager.hasAnyRole(Jsr250AuthorizationManager.this.rolePrefix,
+ rolesAllowed.value());
+ }
+ }
+ return NULL_MANAGER;
+ }
+
+ private Set findJsr250Annotations(Method method, Class> targetClass) {
+ Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
+ Set annotations = findAnnotations(specificMethod);
+ return (annotations.isEmpty()) ? findAnnotations(specificMethod.getDeclaringClass()) : annotations;
+ }
+
+ private Set findAnnotations(AnnotatedElement annotatedElement) {
+ return AnnotatedElementUtils.findAllMergedAnnotations(annotatedElement, JSR250_ANNOTATIONS);
+ }
+
+ }
+
+}
diff --git a/core/src/main/java/org/springframework/security/access/annotation/SecuredAuthorizationManager.java b/core/src/main/java/org/springframework/security/access/annotation/SecuredAuthorizationManager.java
new file mode 100644
index 00000000000..ca962669f26
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/access/annotation/SecuredAuthorizationManager.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2002-2021 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.access.annotation;
+
+import java.lang.reflect.Method;
+import java.util.function.Supplier;
+
+import org.aopalliance.intercept.MethodInvocation;
+
+import org.springframework.aop.support.AopUtils;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.lang.NonNull;
+import org.springframework.security.access.method.MethodAuthorizationContext;
+import org.springframework.security.authorization.AuthorityAuthorizationManager;
+import org.springframework.security.authorization.AuthorizationDecision;
+import org.springframework.security.authorization.AuthorizationManager;
+import org.springframework.security.core.Authentication;
+
+/**
+ * An {@link AuthorizationManager} which can determine if an {@link Authentication} has
+ * access to the {@link MethodInvocation} by evaluating if the {@link Authentication}
+ * contains a specified authority from the Spring Security's {@link Secured} annotation.
+ *
+ * @author Evgeniy Cheban
+ * @since 5.5
+ */
+public final class SecuredAuthorizationManager implements AuthorizationManager {
+
+ private final SecuredAuthorizationManagerRegistry registry = new SecuredAuthorizationManagerRegistry();
+
+ /**
+ * Determines if an {@link Authentication} has access to the {@link MethodInvocation}
+ * by evaluating if the {@link Authentication} contains a specified authority from the
+ * Spring Security's {@link Secured} annotation.
+ * @param authentication the {@link Supplier} of the {@link Authentication} to check
+ * @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check
+ * @return an {@link AuthorizationDecision} or null if the {@link Secured} annotation
+ * is not present
+ */
+ @Override
+ public AuthorizationDecision check(Supplier authentication,
+ MethodAuthorizationContext methodAuthorizationContext) {
+ AuthorizationManager delegate = this.registry
+ .getManager(methodAuthorizationContext);
+ return delegate.check(authentication, methodAuthorizationContext);
+ }
+
+ private static final class SecuredAuthorizationManagerRegistry extends AbstractAuthorizationManagerRegistry {
+
+ @NonNull
+ @Override
+ AuthorizationManager resolveManager(Method method, Class> targetClass) {
+ Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
+ Secured secured = findSecuredAnnotation(specificMethod);
+ return (secured != null) ? AuthorityAuthorizationManager.hasAnyAuthority(secured.value()) : NULL_MANAGER;
+ }
+
+ private Secured findSecuredAnnotation(Method method) {
+ Secured secured = AnnotationUtils.findAnnotation(method, Secured.class);
+ return (secured != null) ? secured
+ : AnnotationUtils.findAnnotation(method.getDeclaringClass(), Secured.class);
+ }
+
+ }
+
+}
diff --git a/core/src/main/java/org/springframework/security/access/intercept/aopalliance/AuthorizationMethodInterceptor.java b/core/src/main/java/org/springframework/security/access/intercept/aopalliance/AuthorizationMethodInterceptor.java
new file mode 100644
index 00000000000..37de6d2df6e
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/access/intercept/aopalliance/AuthorizationMethodInterceptor.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2002-2021 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.access.intercept.aopalliance;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+
+import org.springframework.aop.support.AopUtils;
+import org.springframework.lang.NonNull;
+import org.springframework.security.access.method.AuthorizationMethodAfterAdvice;
+import org.springframework.security.access.method.AuthorizationMethodBeforeAdvice;
+import org.springframework.security.access.method.MethodAuthorizationContext;
+import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+/**
+ * Provides security interception of AOP Alliance based method invocations.
+ *
+ * @author Evgeniy Cheban
+ * @since 5.5
+ */
+public final class AuthorizationMethodInterceptor implements MethodInterceptor {
+
+ private final AuthorizationMethodBeforeAdvice beforeAdvice;
+
+ private final AuthorizationMethodAfterAdvice afterAdvice;
+
+ /**
+ * Creates an instance.
+ * @param beforeAdvice the {@link AuthorizationMethodBeforeAdvice} to use
+ * @param afterAdvice the {@link AuthorizationMethodAfterAdvice} to use
+ */
+ public AuthorizationMethodInterceptor(AuthorizationMethodBeforeAdvice beforeAdvice,
+ AuthorizationMethodAfterAdvice afterAdvice) {
+ this.beforeAdvice = beforeAdvice;
+ this.afterAdvice = afterAdvice;
+ }
+
+ /**
+ * This method should be used to enforce security on a {@link MethodInvocation}.
+ * @param mi the method being invoked which requires a security decision
+ * @return the returned value from the {@link MethodInvocation}
+ */
+ @Override
+ public Object invoke(@NonNull MethodInvocation mi) throws Throwable {
+ MethodAuthorizationContext methodAuthorizationContext = getMethodAuthorizationContext(mi);
+ this.beforeAdvice.before(this::getAuthentication, methodAuthorizationContext);
+ Object returnedObject = mi.proceed();
+ return this.afterAdvice.after(this::getAuthentication, methodAuthorizationContext, returnedObject);
+ }
+
+ private MethodAuthorizationContext getMethodAuthorizationContext(MethodInvocation mi) {
+ Object target = mi.getThis();
+ Class> targetClass = (target != null) ? AopUtils.getTargetClass(target) : null;
+ return new MethodAuthorizationContext(mi, targetClass);
+ }
+
+ private Authentication getAuthentication() {
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+ if (authentication == null) {
+ throw new AuthenticationCredentialsNotFoundException(
+ "An Authentication object was not found in the SecurityContext");
+ }
+ return authentication;
+ }
+
+}
diff --git a/core/src/main/java/org/springframework/security/access/method/AuthorizationManagerMethodAfterAdvice.java b/core/src/main/java/org/springframework/security/access/method/AuthorizationManagerMethodAfterAdvice.java
new file mode 100644
index 00000000000..776cc3e032b
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/access/method/AuthorizationManagerMethodAfterAdvice.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2002-2021 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.access.method;
+
+import java.util.function.Supplier;
+
+import org.springframework.aop.MethodMatcher;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.authorization.AuthorizationManager;
+import org.springframework.security.core.Authentication;
+import org.springframework.util.Assert;
+
+/**
+ * An {@link AuthorizationMethodAfterAdvice} which can determine if an
+ * {@link Authentication} has access to the {@link T} object using an
+ * {@link AuthorizationManager} if a {@link MethodMatcher} matches.
+ *
+ * @param the type of object that the authorization check is being done one.
+ * @author Evgeniy Cheban
+ * @since 5.5
+ */
+public final class AuthorizationManagerMethodAfterAdvice implements AuthorizationMethodAfterAdvice {
+
+ private final MethodMatcher methodMatcher;
+
+ private final AuthorizationManager authorizationManager;
+
+ /**
+ * Creates an instance.
+ * @param methodMatcher the {@link MethodMatcher} to use
+ * @param authorizationManager the {@link AuthorizationManager} to use
+ */
+ public AuthorizationManagerMethodAfterAdvice(MethodMatcher methodMatcher,
+ AuthorizationManager authorizationManager) {
+ Assert.notNull(methodMatcher, "methodMatcher cannot be null");
+ Assert.notNull(authorizationManager, "authorizationManager cannot be null");
+ this.methodMatcher = methodMatcher;
+ this.authorizationManager = authorizationManager;
+ }
+
+ /**
+ * Determines if an {@link Authentication} has access to the {@link T} object using
+ * the {@link AuthorizationManager}.
+ * @param authentication the {@link Supplier} of the {@link Authentication} to check
+ * @param object the {@link T} object to check
+ * @throws AccessDeniedException if access is not granted
+ */
+ @Override
+ public Object after(Supplier authentication, T object, Object returnedObject) {
+ this.authorizationManager.verify(authentication, object);
+ return returnedObject;
+ }
+
+ @Override
+ public MethodMatcher getMethodMatcher() {
+ return this.methodMatcher;
+ }
+
+}
diff --git a/core/src/main/java/org/springframework/security/access/method/AuthorizationManagerMethodBeforeAdvice.java b/core/src/main/java/org/springframework/security/access/method/AuthorizationManagerMethodBeforeAdvice.java
new file mode 100644
index 00000000000..e8d583d92f2
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/access/method/AuthorizationManagerMethodBeforeAdvice.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2002-2021 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.access.method;
+
+import java.util.function.Supplier;
+
+import org.springframework.aop.MethodMatcher;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.authorization.AuthorizationManager;
+import org.springframework.security.core.Authentication;
+import org.springframework.util.Assert;
+
+/**
+ * An {@link AuthorizationMethodBeforeAdvice} which can determine if an
+ * {@link Authentication} has access to the {@link T} object using an
+ * {@link AuthorizationManager} if a {@link MethodMatcher} matches.
+ *
+ * @param the type of object that the authorization check is being done one.
+ * @author Evgeniy Cheban
+ * @since 5.5
+ */
+public final class AuthorizationManagerMethodBeforeAdvice implements AuthorizationMethodBeforeAdvice {
+
+ private final MethodMatcher methodMatcher;
+
+ private final AuthorizationManager authorizationManager;
+
+ /**
+ * Creates an instance.
+ * @param methodMatcher the {@link MethodMatcher} to use
+ * @param authorizationManager the {@link AuthorizationManager} to use
+ */
+ public AuthorizationManagerMethodBeforeAdvice(MethodMatcher methodMatcher,
+ AuthorizationManager authorizationManager) {
+ Assert.notNull(methodMatcher, "methodMatcher cannot be null");
+ Assert.notNull(authorizationManager, "authorizationManager cannot be null");
+ this.methodMatcher = methodMatcher;
+ this.authorizationManager = authorizationManager;
+ }
+
+ /**
+ * Determines if an {@link Authentication} has access to the {@link T} object using
+ * the {@link AuthorizationManager}.
+ * @param authentication the {@link Supplier} of the {@link Authentication} to check
+ * @param object the {@link T} object to check
+ * @throws AccessDeniedException if access is not granted
+ */
+ @Override
+ public void before(Supplier authentication, T object) {
+ this.authorizationManager.verify(authentication, object);
+ }
+
+ @Override
+ public MethodMatcher getMethodMatcher() {
+ return this.methodMatcher;
+ }
+
+}
diff --git a/core/src/main/java/org/springframework/security/access/method/AuthorizationMethodAfterAdvice.java b/core/src/main/java/org/springframework/security/access/method/AuthorizationMethodAfterAdvice.java
new file mode 100644
index 00000000000..ac95eb62029
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/access/method/AuthorizationMethodAfterAdvice.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2002-2021 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.access.method;
+
+import java.util.function.Supplier;
+
+import org.aopalliance.intercept.MethodInvocation;
+
+import org.springframework.aop.ClassFilter;
+import org.springframework.aop.Pointcut;
+import org.springframework.security.core.Authentication;
+
+/**
+ * An Authorization advice that can determine if an {@link Authentication} has access to
+ * the returned object from the {@link MethodInvocation}. The {@link #getMethodMatcher()}
+ * describes when the advice applies for the method.
+ *
+ * @param the type of object that the authorization check is being done one.
+ * @author Evgeniy Cheban
+ * @since 5.5
+ */
+public interface AuthorizationMethodAfterAdvice extends Pointcut {
+
+ /**
+ * Returns the default {@link ClassFilter}.
+ * @return the {@link ClassFilter#TRUE} to use
+ */
+ @Override
+ default ClassFilter getClassFilter() {
+ return ClassFilter.TRUE;
+ }
+
+ /**
+ * Determines if an {@link Authentication} has access to the returned object from the
+ * {@link MethodInvocation}.
+ * @param authentication the {@link Supplier} of the {@link Authentication} to check
+ * @param object the {@link T} object to check
+ * @param returnedObject the returned object from the {@link MethodInvocation} to
+ * check
+ * @return the Object that will ultimately be returned to the caller (if
+ * an implementation does not wish to modify the object to be returned to the caller,
+ * the implementation should simply return the same object it was passed by the
+ * returnedObject method argument)
+ */
+ Object after(Supplier authentication, T object, Object returnedObject);
+
+}
diff --git a/core/src/main/java/org/springframework/security/access/method/AuthorizationMethodBeforeAdvice.java b/core/src/main/java/org/springframework/security/access/method/AuthorizationMethodBeforeAdvice.java
new file mode 100644
index 00000000000..d367d525147
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/access/method/AuthorizationMethodBeforeAdvice.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2002-2021 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.access.method;
+
+import java.util.function.Supplier;
+
+import org.springframework.aop.ClassFilter;
+import org.springframework.aop.Pointcut;
+import org.springframework.security.core.Authentication;
+
+/**
+ * An advice which can determine if an {@link Authentication} has access to the {@link T}
+ * object. The {@link #getMethodMatcher()} describes when the advice applies for the
+ * method.
+ *
+ * @param the type of object that the authorization check is being done one.
+ * @author Evgeniy Cheban
+ * @since 5.5
+ */
+public interface AuthorizationMethodBeforeAdvice extends Pointcut {
+
+ /**
+ * Returns the default {@link ClassFilter}.
+ * @return the {@link ClassFilter#TRUE} to use
+ */
+ @Override
+ default ClassFilter getClassFilter() {
+ return ClassFilter.TRUE;
+ }
+
+ /**
+ * Determines if an {@link Authentication} has access to the {@link T} object.
+ * @param authentication the {@link Supplier} of the {@link Authentication} to check
+ * @param object the {@link T} object to check
+ */
+ void before(Supplier authentication, T object);
+
+}
diff --git a/core/src/main/java/org/springframework/security/access/method/DelegatingAuthorizationMethodAfterAdvice.java b/core/src/main/java/org/springframework/security/access/method/DelegatingAuthorizationMethodAfterAdvice.java
new file mode 100644
index 00000000000..b4d6c0548db
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/access/method/DelegatingAuthorizationMethodAfterAdvice.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2002-2021 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.access.method;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.function.Supplier;
+
+import org.aopalliance.intercept.MethodInvocation;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.aop.MethodMatcher;
+import org.springframework.aop.support.StaticMethodMatcher;
+import org.springframework.core.log.LogMessage;
+import org.springframework.security.core.Authentication;
+
+/**
+ * An {@link AuthorizationMethodAfterAdvice} which delegates to specific
+ * {@link AuthorizationMethodAfterAdvice}s and returns the result (possibly modified) from
+ * the {@link MethodInvocation}.
+ *
+ * @author Evgeniy Cheban
+ * @since 5.5
+ */
+public final class DelegatingAuthorizationMethodAfterAdvice
+ implements AuthorizationMethodAfterAdvice {
+
+ private final Log logger = LogFactory.getLog(getClass());
+
+ private final MethodMatcher methodMatcher = new StaticMethodMatcher() {
+ @Override
+ public boolean matches(Method method, Class> targetClass) {
+ for (AuthorizationMethodAfterAdvice delegate : DelegatingAuthorizationMethodAfterAdvice.this.delegates) {
+ MethodMatcher methodMatcher = delegate.getMethodMatcher();
+ if (methodMatcher.matches(method, targetClass)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+
+ private final List> delegates;
+
+ /**
+ * Creates an instance.
+ * @param delegates the {@link AuthorizationMethodAfterAdvice}s to use
+ */
+ public DelegatingAuthorizationMethodAfterAdvice(
+ List> delegates) {
+ this.delegates = delegates;
+ }
+
+ @Override
+ public MethodMatcher getMethodMatcher() {
+ return this.methodMatcher;
+ }
+
+ /**
+ * Delegates to specific {@link AuthorizationMethodAfterAdvice}s and returns the
+ * returnedObject (possibly modified) from the method argument.
+ * @param authentication the {@link Supplier} of the {@link Authentication} to check
+ * @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check
+ * @param returnedObject the returned object from the {@link MethodInvocation} to
+ * check
+ * @return the returnedObject (possibly modified) from the method
+ * argument
+ */
+ @Override
+ public Object after(Supplier authentication, MethodAuthorizationContext methodAuthorizationContext,
+ Object returnedObject) {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(
+ LogMessage.format("Post Authorizing %s from %s", returnedObject, methodAuthorizationContext));
+ }
+ Object result = returnedObject;
+ for (AuthorizationMethodAfterAdvice delegate : this.delegates) {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(LogMessage.format("Checking authorization on %s from %s using %s", result,
+ methodAuthorizationContext, delegate));
+ }
+ result = delegate.after(authentication, methodAuthorizationContext, result);
+ }
+ return result;
+ }
+
+}
diff --git a/core/src/main/java/org/springframework/security/access/method/DelegatingAuthorizationMethodBeforeAdvice.java b/core/src/main/java/org/springframework/security/access/method/DelegatingAuthorizationMethodBeforeAdvice.java
new file mode 100644
index 00000000000..ee4d4a937eb
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/access/method/DelegatingAuthorizationMethodBeforeAdvice.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2002-2021 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.access.method;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.function.Supplier;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.aop.MethodMatcher;
+import org.springframework.aop.support.StaticMethodMatcher;
+import org.springframework.core.log.LogMessage;
+import org.springframework.security.core.Authentication;
+
+/**
+ * An {@link AuthorizationMethodBeforeAdvice} which delegates to a specific
+ * {@link AuthorizationMethodBeforeAdvice} and grants access if all
+ * {@link AuthorizationMethodBeforeAdvice}s granted or abstained. Denies access only if
+ * one of the {@link AuthorizationMethodBeforeAdvice}s denied.
+ *
+ * @author Evgeniy Cheban
+ * @since 5.5
+ */
+public final class DelegatingAuthorizationMethodBeforeAdvice
+ implements AuthorizationMethodBeforeAdvice {
+
+ private final Log logger = LogFactory.getLog(getClass());
+
+ private final MethodMatcher methodMatcher = new StaticMethodMatcher() {
+ @Override
+ public boolean matches(Method method, Class> targetClass) {
+ for (AuthorizationMethodBeforeAdvice delegate : DelegatingAuthorizationMethodBeforeAdvice.this.delegates) {
+ MethodMatcher methodMatcher = delegate.getMethodMatcher();
+ if (methodMatcher.matches(method, targetClass)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+
+ private final List> delegates;
+
+ /**
+ * Creates an instance.
+ * @param delegates the {@link AuthorizationMethodBeforeAdvice}s to use
+ */
+ public DelegatingAuthorizationMethodBeforeAdvice(
+ List> delegates) {
+ this.delegates = delegates;
+ }
+
+ @Override
+ public MethodMatcher getMethodMatcher() {
+ return this.methodMatcher;
+ }
+
+ /**
+ * Delegates to a specific {@link AuthorizationMethodBeforeAdvice} and grants access
+ * if all {@link AuthorizationMethodBeforeAdvice}s granted or abstained. Denies only
+ * if one of the {@link AuthorizationMethodBeforeAdvice}s denied.
+ * @param authentication the {@link Supplier} of the {@link Authentication} to check
+ * @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check
+ */
+ @Override
+ public void before(Supplier authentication, MethodAuthorizationContext methodAuthorizationContext) {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(LogMessage.format("Pre Authorizing %s", methodAuthorizationContext));
+ }
+ for (AuthorizationMethodBeforeAdvice delegate : this.delegates) {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(LogMessage.format("Checking authorization on %s using %s", methodAuthorizationContext,
+ delegate));
+ }
+ delegate.before(authentication, methodAuthorizationContext);
+ }
+ }
+
+}
diff --git a/core/src/main/java/org/springframework/security/access/method/MethodAuthorizationContext.java b/core/src/main/java/org/springframework/security/access/method/MethodAuthorizationContext.java
new file mode 100644
index 00000000000..4361f9ac9a0
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/access/method/MethodAuthorizationContext.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2002-2021 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.access.method;
+
+import org.aopalliance.intercept.MethodInvocation;
+
+/**
+ * An authorization context which is holds the {@link MethodInvocation}, the target class
+ * and the returned object.
+ *
+ * @author Evgeniy Cheban
+ * @since 5.5
+ */
+public final class MethodAuthorizationContext {
+
+ private final MethodInvocation methodInvocation;
+
+ private final Class> targetClass;
+
+ private Object returnObject;
+
+ /**
+ * Creates an instance.
+ * @param methodInvocation the {@link MethodInvocation} to use
+ * @param targetClass the target class to use
+ */
+ public MethodAuthorizationContext(MethodInvocation methodInvocation, Class> targetClass) {
+ this.methodInvocation = methodInvocation;
+ this.targetClass = targetClass;
+ }
+
+ /**
+ * Returns the {@link MethodInvocation}.
+ * @return the {@link MethodInvocation} to use
+ */
+ public MethodInvocation getMethodInvocation() {
+ return this.methodInvocation;
+ }
+
+ /**
+ * Returns the target class.
+ * @return the target class to use
+ */
+ public Class> getTargetClass() {
+ return this.targetClass;
+ }
+
+ /**
+ * Returns the returned object from the {@link MethodInvocation}.
+ * @return the returned object from the {@link MethodInvocation} to use
+ */
+ public Object getReturnObject() {
+ return this.returnObject;
+ }
+
+ /**
+ * Sets the returned object from the {@link MethodInvocation}.
+ * @param returnObject the returned object from the {@link MethodInvocation} to use
+ */
+ public void setReturnObject(Object returnObject) {
+ this.returnObject = returnObject;
+ }
+
+ @Override
+ public String toString() {
+ return "MethodAuthorizationContext[methodInvocation=" + this.methodInvocation + ", targetClass="
+ + this.targetClass + ", returnObject=" + this.returnObject + ']';
+ }
+
+}
diff --git a/core/src/main/java/org/springframework/security/authorization/AuthorityAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/AuthorityAuthorizationManager.java
index ea6f3d80272..7d245aed6ed 100644
--- a/core/src/main/java/org/springframework/security/authorization/AuthorityAuthorizationManager.java
+++ b/core/src/main/java/org/springframework/security/authorization/AuthorityAuthorizationManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2021 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.
@@ -75,9 +75,22 @@ public static AuthorityAuthorizationManager hasAuthority(String authority
* @return the new instance
*/
public static AuthorityAuthorizationManager hasAnyRole(String... roles) {
+ return hasAnyRole(ROLE_PREFIX, roles);
+ }
+
+ /**
+ * Creates an instance of {@link AuthorityAuthorizationManager} with the provided
+ * authorities.
+ * @param rolePrefix the role prefix for roles
+ * @param roles the authorities to check for prefixed with rolePrefix
+ * @param the type of object being authorized
+ * @return the new instance
+ */
+ public static AuthorityAuthorizationManager hasAnyRole(String rolePrefix, String[] roles) {
+ Assert.notNull(rolePrefix, "rolePrefix cannot be null");
Assert.notEmpty(roles, "roles cannot be empty");
Assert.noNullElements(roles, "roles cannot contain null values");
- return hasAnyAuthority(toNamedRolesArray(roles));
+ return hasAnyAuthority(toNamedRolesArray(rolePrefix, roles));
}
/**
@@ -93,10 +106,10 @@ public static AuthorityAuthorizationManager hasAnyAuthority(String... aut
return new AuthorityAuthorizationManager<>(authorities);
}
- private static String[] toNamedRolesArray(String... roles) {
+ private static String[] toNamedRolesArray(String rolePrefix, String[] roles) {
String[] result = new String[roles.length];
for (int i = 0; i < roles.length; i++) {
- result[i] = ROLE_PREFIX + roles[i];
+ result[i] = rolePrefix + roles[i];
}
return result;
}
diff --git a/core/src/main/java/org/springframework/security/authorization/method/AbstractExpressionAttributeRegistry.java b/core/src/main/java/org/springframework/security/authorization/method/AbstractExpressionAttributeRegistry.java
new file mode 100644
index 00000000000..f48d59dd33c
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/authorization/method/AbstractExpressionAttributeRegistry.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2002-2021 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.method;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.aopalliance.intercept.MethodInvocation;
+
+import org.springframework.core.MethodClassKey;
+import org.springframework.lang.NonNull;
+import org.springframework.security.access.method.MethodAuthorizationContext;
+
+/**
+ * An abstract registry which provides an {@link ExpressionAttribute} for the
+ * {@link MethodInvocation}.
+ *
+ * @author Evgeniy Cheban
+ * @since 5.5
+ */
+abstract class AbstractExpressionAttributeRegistry {
+
+ private final Map cachedAttributes = new ConcurrentHashMap<>();
+
+ /**
+ * Returns an {@link ExpressionAttribute} for the {@link MethodAuthorizationContext}.
+ * @param methodAuthorizationContext the {@link MethodAuthorizationContext} to use
+ * @return the {@link ExpressionAttribute} to use
+ */
+ final T getAttribute(MethodAuthorizationContext methodAuthorizationContext) {
+ MethodInvocation methodInvocation = methodAuthorizationContext.getMethodInvocation();
+ Method method = methodInvocation.getMethod();
+ Class> targetClass = methodAuthorizationContext.getTargetClass();
+ return getAttribute(method, targetClass);
+ }
+
+ /**
+ * Returns an {@link ExpressionAttribute} for the method and the target class.
+ * @param method the method
+ * @param targetClass the target class
+ * @return the {@link ExpressionAttribute} to use
+ */
+ final T getAttribute(Method method, Class> targetClass) {
+ MethodClassKey cacheKey = new MethodClassKey(method, targetClass);
+ return this.cachedAttributes.computeIfAbsent(cacheKey, (k) -> resolveAttribute(method, targetClass));
+ }
+
+ /**
+ * Subclasses should implement this method to provide the non-null
+ * {@link ExpressionAttribute} for the method and the target class.
+ * @param method the method
+ * @param targetClass the target class
+ * @return the non-null {@link ExpressionAttribute}
+ */
+ @NonNull
+ abstract T resolveAttribute(Method method, Class> targetClass);
+
+}
diff --git a/core/src/main/java/org/springframework/security/authorization/method/ExpressionAttribute.java b/core/src/main/java/org/springframework/security/authorization/method/ExpressionAttribute.java
new file mode 100644
index 00000000000..80e49360e9b
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/authorization/method/ExpressionAttribute.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2002-2021 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.method;
+
+import org.springframework.expression.Expression;
+
+/**
+ * An {@link Expression} attribute.
+ *
+ * @author Evgeniy Cheban
+ * @since 5.5
+ */
+class ExpressionAttribute {
+
+ /**
+ * Represents an empty attribute with null {@link Expression}.
+ */
+ static final ExpressionAttribute NULL_ATTRIBUTE = new ExpressionAttribute(null);
+
+ private final Expression expression;
+
+ /**
+ * Creates an instance.
+ * @param expression the {@link Expression} to use
+ */
+ ExpressionAttribute(Expression expression) {
+ this.expression = expression;
+ }
+
+ /**
+ * Returns the {@link Expression}.
+ * @return the {@link Expression} to use
+ */
+ Expression getExpression() {
+ return this.expression;
+ }
+
+}
diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java
new file mode 100644
index 00000000000..11109fb67ed
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2002-2021 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.method;
+
+import java.lang.reflect.Method;
+import java.util.function.Supplier;
+
+import org.aopalliance.intercept.MethodInvocation;
+import reactor.util.annotation.NonNull;
+
+import org.springframework.aop.support.AopUtils;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.Expression;
+import org.springframework.security.access.expression.ExpressionUtils;
+import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
+import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
+import org.springframework.security.access.method.MethodAuthorizationContext;
+import org.springframework.security.access.prepost.PostAuthorize;
+import org.springframework.security.authorization.AuthorizationDecision;
+import org.springframework.security.authorization.AuthorizationManager;
+import org.springframework.security.core.Authentication;
+import org.springframework.util.Assert;
+
+/**
+ * An {@link AuthorizationManager} which can determine if an {@link Authentication} has
+ * access to the {@link MethodInvocation} by evaluating an expression from the
+ * {@link PostAuthorize} annotation.
+ *
+ * @author Evgeniy Cheban
+ * @since 5.5
+ */
+public final class PostAuthorizeAuthorizationManager implements AuthorizationManager {
+
+ private final PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
+
+ private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
+
+ /**
+ * Sets the {@link MethodSecurityExpressionHandler}.
+ * @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
+ */
+ public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
+ Assert.notNull(expressionHandler, "expressionHandler cannot be null");
+ this.expressionHandler = expressionHandler;
+ }
+
+ /**
+ * Determines if an {@link Authentication} has access to the {@link MethodInvocation}
+ * by evaluating an expression from the {@link PostAuthorize} annotation.
+ * @param authentication the {@link Supplier} of the {@link Authentication} to check
+ * @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check
+ * @return an {@link AuthorizationDecision} or null if the {@link PostAuthorize}
+ * annotation is not present
+ */
+ @Override
+ public AuthorizationDecision check(Supplier authentication,
+ MethodAuthorizationContext methodAuthorizationContext) {
+ ExpressionAttribute attribute = this.registry.getAttribute(methodAuthorizationContext);
+ if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
+ return null;
+ }
+ EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(),
+ methodAuthorizationContext.getMethodInvocation());
+ this.expressionHandler.setReturnObject(methodAuthorizationContext.getReturnObject(), ctx);
+ boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx);
+ return new AuthorizationDecision(granted);
+ }
+
+ private final class PostAuthorizeExpressionAttributeRegistry
+ extends AbstractExpressionAttributeRegistry {
+
+ @NonNull
+ @Override
+ ExpressionAttribute resolveAttribute(Method method, Class> targetClass) {
+ Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
+ PostAuthorize postAuthorize = findPostAuthorizeAnnotation(specificMethod);
+ if (postAuthorize == null) {
+ return ExpressionAttribute.NULL_ATTRIBUTE;
+ }
+ Expression postAuthorizeExpression = PostAuthorizeAuthorizationManager.this.expressionHandler
+ .getExpressionParser().parseExpression(postAuthorize.value());
+ return new ExpressionAttribute(postAuthorizeExpression);
+ }
+
+ private PostAuthorize findPostAuthorizeAnnotation(Method method) {
+ PostAuthorize postAuthorize = AnnotationUtils.findAnnotation(method, PostAuthorize.class);
+ return (postAuthorize != null) ? postAuthorize
+ : AnnotationUtils.findAnnotation(method.getDeclaringClass(), PostAuthorize.class);
+ }
+
+ }
+
+}
diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodAfterAdvice.java b/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodAfterAdvice.java
new file mode 100644
index 00000000000..c573922c9c9
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodAfterAdvice.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2002-2021 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.method;
+
+import java.lang.reflect.Method;
+import java.util.function.Supplier;
+
+import org.aopalliance.intercept.MethodInvocation;
+
+import org.springframework.aop.MethodMatcher;
+import org.springframework.aop.support.AopUtils;
+import org.springframework.aop.support.StaticMethodMatcher;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.Expression;
+import org.springframework.lang.NonNull;
+import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
+import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
+import org.springframework.security.access.method.AuthorizationMethodAfterAdvice;
+import org.springframework.security.access.method.MethodAuthorizationContext;
+import org.springframework.security.access.prepost.PostFilter;
+import org.springframework.security.core.Authentication;
+import org.springframework.util.Assert;
+
+/**
+ * An {@link AuthorizationMethodAfterAdvice} which filters a returnedObject
+ * from the {@link MethodInvocation} by evaluating an expression from the
+ * {@link PostFilter} annotation.
+ *
+ * @author Evgeniy Cheban
+ * @since 5.5
+ */
+public final class PostFilterAuthorizationMethodAfterAdvice
+ implements AuthorizationMethodAfterAdvice {
+
+ private final PostFilterExpressionAttributeRegistry registry = new PostFilterExpressionAttributeRegistry();
+
+ private final MethodMatcher methodMatcher = new StaticMethodMatcher() {
+ @Override
+ public boolean matches(Method method, Class> targetClass) {
+ return PostFilterAuthorizationMethodAfterAdvice.this.registry.getAttribute(method,
+ targetClass) != ExpressionAttribute.NULL_ATTRIBUTE;
+ }
+ };
+
+ private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
+
+ /**
+ * Sets the {@link MethodSecurityExpressionHandler}.
+ * @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
+ */
+ public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
+ Assert.notNull(expressionHandler, "expressionHandler cannot be null");
+ this.expressionHandler = expressionHandler;
+ }
+
+ @Override
+ public MethodMatcher getMethodMatcher() {
+ return this.methodMatcher;
+ }
+
+ /**
+ * Filters a returnedObject from the {@link MethodInvocation} by
+ * evaluating an expression from the {@link PostFilter} annotation.
+ * @param authentication the {@link Supplier} of the {@link Authentication} to check
+ * @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check
+ * @param returnedObject the returned object from the {@link MethodInvocation} to
+ * check
+ * @return filtered returnedObject from the {@link MethodInvocation}
+ */
+ @Override
+ public Object after(Supplier authentication, MethodAuthorizationContext methodAuthorizationContext,
+ Object returnedObject) {
+ if (returnedObject == null) {
+ return null;
+ }
+ ExpressionAttribute attribute = this.registry.getAttribute(methodAuthorizationContext);
+ if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
+ return returnedObject;
+ }
+ EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(),
+ methodAuthorizationContext.getMethodInvocation());
+ Object result = this.expressionHandler.filter(returnedObject, attribute.getExpression(), ctx);
+ methodAuthorizationContext.setReturnObject(result);
+ return result;
+ }
+
+ private final class PostFilterExpressionAttributeRegistry
+ extends AbstractExpressionAttributeRegistry {
+
+ @NonNull
+ @Override
+ ExpressionAttribute resolveAttribute(Method method, Class> targetClass) {
+ Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
+ PostFilter postFilter = findPostFilterAnnotation(specificMethod);
+ if (postFilter == null) {
+ return ExpressionAttribute.NULL_ATTRIBUTE;
+ }
+ Expression postFilterExpression = PostFilterAuthorizationMethodAfterAdvice.this.expressionHandler
+ .getExpressionParser().parseExpression(postFilter.value());
+ return new ExpressionAttribute(postFilterExpression);
+ }
+
+ private PostFilter findPostFilterAnnotation(Method method) {
+ PostFilter postFilter = AnnotationUtils.findAnnotation(method, PostFilter.class);
+ return (postFilter != null) ? postFilter
+ : AnnotationUtils.findAnnotation(method.getDeclaringClass(), PostFilter.class);
+ }
+
+ }
+
+}
diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java
new file mode 100644
index 00000000000..bebceee0e64
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2002-2021 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.method;
+
+import java.lang.reflect.Method;
+import java.util.function.Supplier;
+
+import org.aopalliance.intercept.MethodInvocation;
+import reactor.util.annotation.NonNull;
+
+import org.springframework.aop.support.AopUtils;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.Expression;
+import org.springframework.security.access.expression.ExpressionUtils;
+import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
+import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
+import org.springframework.security.access.method.MethodAuthorizationContext;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.authorization.AuthorizationDecision;
+import org.springframework.security.authorization.AuthorizationManager;
+import org.springframework.security.core.Authentication;
+import org.springframework.util.Assert;
+
+/**
+ * An {@link AuthorizationManager} which can determine if an {@link Authentication} has
+ * access to the {@link MethodInvocation} by evaluating an expression from the
+ * {@link PreAuthorize} annotation.
+ *
+ * @author Evgeniy Cheban
+ * @since 5.5
+ */
+public final class PreAuthorizeAuthorizationManager implements AuthorizationManager {
+
+ private final PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry();
+
+ private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
+
+ /**
+ * Sets the {@link MethodSecurityExpressionHandler}.
+ * @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
+ */
+ public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
+ Assert.notNull(expressionHandler, "expressionHandler cannot be null");
+ this.expressionHandler = expressionHandler;
+ }
+
+ /**
+ * Determines if an {@link Authentication} has access to the {@link MethodInvocation}
+ * by evaluating an expression from the {@link PreAuthorize} annotation.
+ * @param authentication the {@link Supplier} of the {@link Authentication} to check
+ * @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check
+ * @return an {@link AuthorizationDecision} or null if the {@link PreAuthorize}
+ * annotation is not present
+ */
+ @Override
+ public AuthorizationDecision check(Supplier authentication,
+ MethodAuthorizationContext methodAuthorizationContext) {
+ ExpressionAttribute attribute = this.registry.getAttribute(methodAuthorizationContext);
+ if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
+ return null;
+ }
+ EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(),
+ methodAuthorizationContext.getMethodInvocation());
+ boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx);
+ return new AuthorizationDecision(granted);
+ }
+
+ private final class PreAuthorizeExpressionAttributeRegistry
+ extends AbstractExpressionAttributeRegistry {
+
+ @NonNull
+ @Override
+ ExpressionAttribute resolveAttribute(Method method, Class> targetClass) {
+ Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
+ PreAuthorize preAuthorize = findPreAuthorizeAnnotation(specificMethod);
+ if (preAuthorize == null) {
+ return ExpressionAttribute.NULL_ATTRIBUTE;
+ }
+ Expression preAuthorizeExpression = PreAuthorizeAuthorizationManager.this.expressionHandler
+ .getExpressionParser().parseExpression(preAuthorize.value());
+ return new ExpressionAttribute(preAuthorizeExpression);
+ }
+
+ private PreAuthorize findPreAuthorizeAnnotation(Method method) {
+ PreAuthorize preAuthorize = AnnotationUtils.findAnnotation(method, PreAuthorize.class);
+ return (preAuthorize != null) ? preAuthorize
+ : AnnotationUtils.findAnnotation(method.getDeclaringClass(), PreAuthorize.class);
+ }
+
+ }
+
+}
diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodBeforeAdvice.java b/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodBeforeAdvice.java
new file mode 100644
index 00000000000..93ab3621030
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodBeforeAdvice.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2002-2021 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.method;
+
+import java.lang.reflect.Method;
+import java.util.function.Supplier;
+
+import org.aopalliance.intercept.MethodInvocation;
+
+import org.springframework.aop.MethodMatcher;
+import org.springframework.aop.support.AopUtils;
+import org.springframework.aop.support.StaticMethodMatcher;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.Expression;
+import org.springframework.lang.NonNull;
+import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
+import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
+import org.springframework.security.access.method.AuthorizationMethodBeforeAdvice;
+import org.springframework.security.access.method.MethodAuthorizationContext;
+import org.springframework.security.access.prepost.PreFilter;
+import org.springframework.security.core.Authentication;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * An {@link AuthorizationMethodBeforeAdvice} which filters a method argument by
+ * evaluating an expression from the {@link PreFilter} annotation.
+ *
+ * @author Evgeniy Cheban
+ * @since 5.5
+ */
+public final class PreFilterAuthorizationMethodBeforeAdvice
+ implements AuthorizationMethodBeforeAdvice {
+
+ private final PreFilterExpressionAttributeRegistry registry = new PreFilterExpressionAttributeRegistry();
+
+ private final MethodMatcher methodMatcher = new StaticMethodMatcher() {
+ @Override
+ public boolean matches(Method method, Class> targetClass) {
+ return PreFilterAuthorizationMethodBeforeAdvice.this.registry.getAttribute(method,
+ targetClass) != PreFilterExpressionAttribute.NULL_ATTRIBUTE;
+ }
+ };
+
+ private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
+
+ /**
+ * Sets the {@link MethodSecurityExpressionHandler}.
+ * @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
+ */
+ public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
+ Assert.notNull(expressionHandler, "expressionHandler cannot be null");
+ this.expressionHandler = expressionHandler;
+ }
+
+ @Override
+ public MethodMatcher getMethodMatcher() {
+ return this.methodMatcher;
+ }
+
+ /**
+ * Filters a method argument by evaluating an expression from the {@link PreFilter}
+ * annotation.
+ * @param authentication the {@link Supplier} of the {@link Authentication} to check
+ * @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check
+ */
+ @Override
+ public void before(Supplier authentication, MethodAuthorizationContext methodAuthorizationContext) {
+ PreFilterExpressionAttribute attribute = this.registry.getAttribute(methodAuthorizationContext);
+ if (attribute == PreFilterExpressionAttribute.NULL_ATTRIBUTE) {
+ return;
+ }
+ MethodInvocation mi = methodAuthorizationContext.getMethodInvocation();
+ EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(), mi);
+ Object filterTarget = findFilterTarget(attribute.filterTarget, ctx, mi);
+ this.expressionHandler.filter(filterTarget, attribute.getExpression(), ctx);
+ }
+
+ private Object findFilterTarget(String filterTargetName, EvaluationContext ctx, MethodInvocation methodInvocation) {
+ Object filterTarget;
+ if (StringUtils.hasText(filterTargetName)) {
+ filterTarget = ctx.lookupVariable(filterTargetName);
+ Assert.notNull(filterTarget, () -> "Filter target was null, or no argument with name '" + filterTargetName
+ + "' found in method.");
+ }
+ else {
+ Object[] arguments = methodInvocation.getArguments();
+ Assert.state(arguments.length == 1,
+ "Unable to determine the method argument for filtering. Specify the filter target.");
+ filterTarget = arguments[0];
+ Assert.notNull(filterTarget,
+ "Filter target was null. Make sure you passing the correct value in the method argument.");
+ }
+ Assert.state(!filterTarget.getClass().isArray(),
+ "Pre-filtering on array types is not supported. Using a Collection will solve this problem.");
+ return filterTarget;
+ }
+
+ private final class PreFilterExpressionAttributeRegistry
+ extends AbstractExpressionAttributeRegistry {
+
+ @NonNull
+ @Override
+ PreFilterExpressionAttribute resolveAttribute(Method method, Class> targetClass) {
+ Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
+ PreFilter preFilter = findPreFilterAnnotation(specificMethod);
+ if (preFilter == null) {
+ return PreFilterExpressionAttribute.NULL_ATTRIBUTE;
+ }
+ Expression preFilterExpression = PreFilterAuthorizationMethodBeforeAdvice.this.expressionHandler
+ .getExpressionParser().parseExpression(preFilter.value());
+ return new PreFilterExpressionAttribute(preFilterExpression, preFilter.filterTarget());
+ }
+
+ private PreFilter findPreFilterAnnotation(Method method) {
+ PreFilter preFilter = AnnotationUtils.findAnnotation(method, PreFilter.class);
+ return (preFilter != null) ? preFilter
+ : AnnotationUtils.findAnnotation(method.getDeclaringClass(), PreFilter.class);
+ }
+
+ }
+
+ private static final class PreFilterExpressionAttribute extends ExpressionAttribute {
+
+ private static final PreFilterExpressionAttribute NULL_ATTRIBUTE = new PreFilterExpressionAttribute(null, null);
+
+ private final String filterTarget;
+
+ private PreFilterExpressionAttribute(Expression expression, String filterTarget) {
+ super(expression);
+ this.filterTarget = filterTarget;
+ }
+
+ }
+
+}
diff --git a/core/src/test/java/org/springframework/security/access/annotation/Jsr250AuthorizationManagerTests.java b/core/src/test/java/org/springframework/security/access/annotation/Jsr250AuthorizationManagerTests.java
new file mode 100644
index 00000000000..a8cdf3273bb
--- /dev/null
+++ b/core/src/test/java/org/springframework/security/access/annotation/Jsr250AuthorizationManagerTests.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2002-2021 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.access.annotation;
+
+import java.util.function.Supplier;
+
+import javax.annotation.security.DenyAll;
+import javax.annotation.security.PermitAll;
+import javax.annotation.security.RolesAllowed;
+
+import org.junit.Test;
+
+import org.springframework.security.access.intercept.method.MockMethodInvocation;
+import org.springframework.security.access.method.MethodAuthorizationContext;
+import org.springframework.security.authentication.TestAuthentication;
+import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.authorization.AuthorizationDecision;
+import org.springframework.security.core.Authentication;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+
+/**
+ * Tests for {@link Jsr250AuthorizationManager}.
+ *
+ * @author Evgeniy Cheban
+ */
+public class Jsr250AuthorizationManagerTests {
+
+ @Test
+ public void rolePrefixWhenNotSetThenDefaultsToRole() {
+ Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
+ assertThat(manager).extracting("rolePrefix").isEqualTo("ROLE_");
+ }
+
+ @Test
+ public void setRolePrefixWhenNullThenException() {
+ Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
+ assertThatIllegalArgumentException().isThrownBy(() -> manager.setRolePrefix(null))
+ .withMessage("rolePrefix cannot be null");
+ }
+
+ @Test
+ public void setRolePrefixWhenNotNullThenSets() {
+ Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
+ manager.setRolePrefix("CUSTOM_");
+ assertThat(manager).extracting("rolePrefix").isEqualTo("CUSTOM_");
+ }
+
+ @Test
+ public void checkDoSomethingWhenNoJsr250AnnotationsThenNullDecision() throws Exception {
+ MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+ "doSomething");
+ MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
+ TestClass.class);
+ Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
+ AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
+ methodAuthorizationContext);
+ assertThat(decision).isNull();
+ }
+
+ @Test
+ public void checkPermitAllRolesAllowedAdminWhenRoleUserThenGrantedDecision() throws Exception {
+ MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+ "permitAllRolesAllowedAdmin");
+ MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
+ TestClass.class);
+ Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
+ AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
+ methodAuthorizationContext);
+ assertThat(decision).isNotNull();
+ assertThat(decision.isGranted()).isTrue();
+ }
+
+ @Test
+ public void checkDenyAllRolesAllowedAdminWhenRoleAdminThenDeniedDecision() throws Exception {
+ MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+ "denyAllRolesAllowedAdmin");
+ MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
+ TestClass.class);
+ Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
+ AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedAdmin,
+ methodAuthorizationContext);
+ assertThat(decision).isNotNull();
+ assertThat(decision.isGranted()).isFalse();
+ }
+
+ @Test
+ public void checkRolesAllowedUserOrAdminWhenRoleUserThenGrantedDecision() throws Exception {
+ MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+ "rolesAllowedUserOrAdmin");
+ MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
+ TestClass.class);
+ Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
+ AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
+ methodAuthorizationContext);
+ assertThat(decision).isNotNull();
+ assertThat(decision.isGranted()).isTrue();
+ }
+
+ @Test
+ public void checkRolesAllowedUserOrAdminWhenRoleAdminThenGrantedDecision() throws Exception {
+ MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+ "rolesAllowedUserOrAdmin");
+ MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
+ TestClass.class);
+ Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
+ AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedAdmin,
+ methodAuthorizationContext);
+ assertThat(decision).isNotNull();
+ assertThat(decision.isGranted()).isTrue();
+ }
+
+ @Test
+ public void checkRolesAllowedUserOrAdminWhenRoleAnonymousThenDeniedDecision() throws Exception {
+ Supplier authentication = () -> new TestingAuthenticationToken("user", "password",
+ "ROLE_ANONYMOUS");
+ MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+ "rolesAllowedUserOrAdmin");
+ MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
+ TestClass.class);
+ Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
+ AuthorizationDecision decision = manager.check(authentication, methodAuthorizationContext);
+ assertThat(decision).isNotNull();
+ assertThat(decision.isGranted()).isFalse();
+ }
+
+ public static class TestClass {
+
+ public void doSomething() {
+
+ }
+
+ @DenyAll
+ @RolesAllowed("ADMIN")
+ public void denyAllRolesAllowedAdmin() {
+
+ }
+
+ @PermitAll
+ @RolesAllowed("ADMIN")
+ public void permitAllRolesAllowedAdmin() {
+
+ }
+
+ @RolesAllowed({ "USER", "ADMIN" })
+ public void rolesAllowedUserOrAdmin() {
+
+ }
+
+ }
+
+}
diff --git a/core/src/test/java/org/springframework/security/access/annotation/SecuredAuthorizationManagerTests.java b/core/src/test/java/org/springframework/security/access/annotation/SecuredAuthorizationManagerTests.java
new file mode 100644
index 00000000000..0d94919219b
--- /dev/null
+++ b/core/src/test/java/org/springframework/security/access/annotation/SecuredAuthorizationManagerTests.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2002-2021 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.access.annotation;
+
+import java.util.function.Supplier;
+
+import org.junit.Test;
+
+import org.springframework.security.access.intercept.method.MockMethodInvocation;
+import org.springframework.security.access.method.MethodAuthorizationContext;
+import org.springframework.security.authentication.TestAuthentication;
+import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.authorization.AuthorizationDecision;
+import org.springframework.security.core.Authentication;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link SecuredAuthorizationManager}.
+ *
+ * @author Evgeniy Cheban
+ */
+public class SecuredAuthorizationManagerTests {
+
+ @Test
+ public void checkDoSomethingWhenNoSecuredAnnotationThenNullDecision() throws Exception {
+ MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+ "doSomething");
+ MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
+ TestClass.class);
+ SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
+ AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
+ methodAuthorizationContext);
+ assertThat(decision).isNull();
+ }
+
+ @Test
+ public void checkSecuredUserOrAdminWhenRoleUserThenGrantedDecision() throws Exception {
+ MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+ "securedUserOrAdmin");
+ MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
+ TestClass.class);
+ SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
+ AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
+ methodAuthorizationContext);
+ assertThat(decision).isNotNull();
+ assertThat(decision.isGranted()).isTrue();
+ }
+
+ @Test
+ public void checkSecuredUserOrAdminWhenRoleAdminThenGrantedDecision() throws Exception {
+ MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+ "securedUserOrAdmin");
+ MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
+ TestClass.class);
+ SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
+ AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedAdmin,
+ methodAuthorizationContext);
+ assertThat(decision).isNotNull();
+ assertThat(decision.isGranted()).isTrue();
+ }
+
+ @Test
+ public void checkSecuredUserOrAdminWhenRoleAnonymousThenDeniedDecision() throws Exception {
+ Supplier authentication = () -> new TestingAuthenticationToken("user", "password",
+ "ROLE_ANONYMOUS");
+ MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+ "securedUserOrAdmin");
+ MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
+ TestClass.class);
+ SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
+ AuthorizationDecision decision = manager.check(authentication, methodAuthorizationContext);
+ assertThat(decision).isNotNull();
+ assertThat(decision.isGranted()).isFalse();
+ }
+
+ public static class TestClass {
+
+ public void doSomething() {
+
+ }
+
+ @Secured({ "ROLE_USER", "ROLE_ADMIN" })
+ public void securedUserOrAdmin() {
+
+ }
+
+ }
+
+}
diff --git a/core/src/test/java/org/springframework/security/access/intercept/aopalliance/AuthorizationMethodInterceptorTests.java b/core/src/test/java/org/springframework/security/access/intercept/aopalliance/AuthorizationMethodInterceptorTests.java
new file mode 100644
index 00000000000..3f647cc9bf7
--- /dev/null
+++ b/core/src/test/java/org/springframework/security/access/intercept/aopalliance/AuthorizationMethodInterceptorTests.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2002-2021 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.access.intercept.aopalliance;
+
+import java.util.function.Supplier;
+
+import org.junit.After;
+import org.junit.Test;
+
+import org.springframework.aop.MethodMatcher;
+import org.springframework.security.access.intercept.method.MockMethodInvocation;
+import org.springframework.security.access.method.AuthorizationMethodAfterAdvice;
+import org.springframework.security.access.method.AuthorizationMethodBeforeAdvice;
+import org.springframework.security.access.method.MethodAuthorizationContext;
+import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
+import org.springframework.security.authentication.TestAuthentication;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.context.SecurityContextImpl;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+
+/**
+ * Tests for {@link AuthorizationMethodInterceptor}.
+ *
+ * @author Evgeniy Cheban
+ */
+public class AuthorizationMethodInterceptorTests {
+
+ @After
+ public void tearDown() {
+ SecurityContextHolder.clearContext();
+ }
+
+ @Test
+ public void invokeWhenAuthenticatedThenVerifyAdvicesUsage() throws Throwable {
+ Authentication authentication = TestAuthentication.authenticatedUser();
+ SecurityContextHolder.setContext(new SecurityContextImpl(authentication));
+ MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+ "doSomethingString");
+ AuthorizationMethodBeforeAdvice mockBeforeAdvice = mock(
+ AuthorizationMethodBeforeAdvice.class);
+ AuthorizationMethodAfterAdvice mockAfterAdvice = mock(
+ AuthorizationMethodAfterAdvice.class);
+ given(mockAfterAdvice.after(any(), any(MethodAuthorizationContext.class), eq(null))).willReturn("abc");
+ AuthorizationMethodInterceptor interceptor = new AuthorizationMethodInterceptor(mockBeforeAdvice,
+ mockAfterAdvice);
+ Object result = interceptor.invoke(mockMethodInvocation);
+ assertThat(result).isEqualTo("abc");
+ verify(mockAfterAdvice).after(any(), any(MethodAuthorizationContext.class), eq(null));
+ }
+
+ @Test
+ public void invokeWhenNotAuthenticatedThenAuthenticationCredentialsNotFoundException() throws Exception {
+ MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+ "doSomethingString");
+ AuthorizationMethodBeforeAdvice beforeAdvice = new AuthorizationMethodBeforeAdvice() {
+ @Override
+ public MethodMatcher getMethodMatcher() {
+ return MethodMatcher.TRUE;
+ }
+
+ @Override
+ public void before(Supplier authentication,
+ MethodAuthorizationContext methodAuthorizationContext) {
+ authentication.get();
+ }
+ };
+ AuthorizationMethodAfterAdvice mockAfterAdvice = mock(
+ AuthorizationMethodAfterAdvice.class);
+ AuthorizationMethodInterceptor interceptor = new AuthorizationMethodInterceptor(beforeAdvice, mockAfterAdvice);
+ assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class)
+ .isThrownBy(() -> interceptor.invoke(mockMethodInvocation))
+ .withMessage("An Authentication object was not found in the SecurityContext");
+ verifyNoInteractions(mockAfterAdvice);
+ }
+
+ public static class TestClass {
+
+ public String doSomethingString() {
+ return null;
+ }
+
+ }
+
+}
diff --git a/core/src/test/java/org/springframework/security/access/method/AuthorizationManagerMethodAfterAdviceTests.java b/core/src/test/java/org/springframework/security/access/method/AuthorizationManagerMethodAfterAdviceTests.java
new file mode 100644
index 00000000000..f2c8a96ea52
--- /dev/null
+++ b/core/src/test/java/org/springframework/security/access/method/AuthorizationManagerMethodAfterAdviceTests.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2002-2021 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.access.method;
+
+import java.util.function.Supplier;
+
+import org.aopalliance.intercept.MethodInvocation;
+import org.junit.Test;
+
+import org.springframework.aop.MethodMatcher;
+import org.springframework.security.authentication.TestAuthentication;
+import org.springframework.security.authorization.AuthorizationManager;
+import org.springframework.security.core.Authentication;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Tests for {@link AuthorizationManagerMethodAfterAdvice}.
+ *
+ * @author Evgeniy Cheban
+ */
+public class AuthorizationManagerMethodAfterAdviceTests {
+
+ @Test
+ public void instantiateWhenMethodMatcherNullThenException() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> new AuthorizationManagerMethodAfterAdvice<>(null, mock(AuthorizationManager.class)))
+ .withMessage("methodMatcher cannot be null");
+ }
+
+ @Test
+ public void instantiateWhenAuthorizationManagerNullThenException() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> new AuthorizationManagerMethodAfterAdvice<>(mock(MethodMatcher.class), null))
+ .withMessage("authorizationManager cannot be null");
+ }
+
+ @Test
+ public void beforeWhenMockAuthorizationManagerThenVerifyAndReturnedObject() {
+ Supplier authentication = TestAuthentication::authenticatedUser;
+ MethodInvocation mockMethodInvocation = mock(MethodInvocation.class);
+ Object returnedObject = new Object();
+ AuthorizationManager mockAuthorizationManager = mock(AuthorizationManager.class);
+ AuthorizationManagerMethodAfterAdvice advice = new AuthorizationManagerMethodAfterAdvice<>(
+ mock(MethodMatcher.class), mockAuthorizationManager);
+ Object result = advice.after(authentication, mockMethodInvocation, returnedObject);
+ assertThat(result).isEqualTo(returnedObject);
+ verify(mockAuthorizationManager).verify(authentication, mockMethodInvocation);
+ }
+
+}
diff --git a/core/src/test/java/org/springframework/security/access/method/AuthorizationManagerMethodBeforeAdviceTests.java b/core/src/test/java/org/springframework/security/access/method/AuthorizationManagerMethodBeforeAdviceTests.java
new file mode 100644
index 00000000000..72da6d2d8fa
--- /dev/null
+++ b/core/src/test/java/org/springframework/security/access/method/AuthorizationManagerMethodBeforeAdviceTests.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2002-2021 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.access.method;
+
+import java.util.function.Supplier;
+
+import org.aopalliance.intercept.MethodInvocation;
+import org.junit.Test;
+
+import org.springframework.aop.MethodMatcher;
+import org.springframework.security.authentication.TestAuthentication;
+import org.springframework.security.authorization.AuthorizationManager;
+import org.springframework.security.core.Authentication;
+
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Tests for {@link AuthorizationManagerMethodBeforeAdvice}.
+ *
+ * @author Evgeniy Cheban
+ */
+public class AuthorizationManagerMethodBeforeAdviceTests {
+
+ @Test
+ public void instantiateWhenMethodMatcherNullThenException() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> new AuthorizationManagerMethodBeforeAdvice<>(null, mock(AuthorizationManager.class)))
+ .withMessage("methodMatcher cannot be null");
+ }
+
+ @Test
+ public void instantiateWhenAuthorizationManagerNullThenException() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> new AuthorizationManagerMethodBeforeAdvice<>(mock(MethodMatcher.class), null))
+ .withMessage("authorizationManager cannot be null");
+ }
+
+ @Test
+ public void beforeWhenMockAuthorizationManagerThenVerify() {
+ Supplier authentication = TestAuthentication::authenticatedUser;
+ MethodInvocation mockMethodInvocation = mock(MethodInvocation.class);
+ AuthorizationManager mockAuthorizationManager = mock(AuthorizationManager.class);
+ AuthorizationManagerMethodBeforeAdvice advice = new AuthorizationManagerMethodBeforeAdvice<>(
+ mock(MethodMatcher.class), mockAuthorizationManager);
+ advice.before(authentication, mockMethodInvocation);
+ verify(mockAuthorizationManager).verify(authentication, mockMethodInvocation);
+ }
+
+}
diff --git a/core/src/test/java/org/springframework/security/access/method/DelegatingAuthorizationMethodAfterAdviceTests.java b/core/src/test/java/org/springframework/security/access/method/DelegatingAuthorizationMethodAfterAdviceTests.java
new file mode 100644
index 00000000000..79bb042457d
--- /dev/null
+++ b/core/src/test/java/org/springframework/security/access/method/DelegatingAuthorizationMethodAfterAdviceTests.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2002-2021 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.access.method;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+import org.junit.Test;
+
+import org.springframework.aop.MethodMatcher;
+import org.springframework.aop.support.StaticMethodMatcher;
+import org.springframework.security.access.intercept.method.MockMethodInvocation;
+import org.springframework.security.authentication.TestAuthentication;
+import org.springframework.security.core.Authentication;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link DelegatingAuthorizationMethodAfterAdvice}.
+ *
+ * @author Evgeniy Cheban
+ */
+public class DelegatingAuthorizationMethodAfterAdviceTests {
+
+ @Test
+ public void methodMatcherWhenNoneMatchesThenNotMatches() throws Exception {
+ List> delegates = new ArrayList<>();
+ delegates.add(new AuthorizationMethodAfterAdvice() {
+ @Override
+ public Object after(Supplier authentication, MethodAuthorizationContext object,
+ Object returnedObject) {
+ return returnedObject;
+ }
+
+ @Override
+ public MethodMatcher getMethodMatcher() {
+ return new StaticMethodMatcher() {
+ @Override
+ public boolean matches(Method method, Class> targetClass) {
+ return false;
+ }
+ };
+ }
+ });
+ delegates.add(new AuthorizationMethodAfterAdvice() {
+ @Override
+ public Object after(Supplier authentication, MethodAuthorizationContext object,
+ Object returnedObject) {
+ return returnedObject;
+ }
+
+ @Override
+ public MethodMatcher getMethodMatcher() {
+ return new StaticMethodMatcher() {
+ @Override
+ public boolean matches(Method method, Class> targetClass) {
+ return false;
+ }
+ };
+ }
+ });
+ DelegatingAuthorizationMethodAfterAdvice advice = new DelegatingAuthorizationMethodAfterAdvice(delegates);
+ MethodMatcher methodMatcher = advice.getMethodMatcher();
+ assertThat(methodMatcher.matches(TestClass.class.getMethod("doSomething"), TestClass.class)).isFalse();
+ }
+
+ @Test
+ public void methodMatcherWhenAnyMatchesThenMatches() throws Exception {
+ List> delegates = new ArrayList<>();
+ delegates.add(new AuthorizationMethodAfterAdvice() {
+ @Override
+ public Object after(Supplier authentication, MethodAuthorizationContext object,
+ Object returnedObject) {
+ return returnedObject;
+ }
+
+ @Override
+ public MethodMatcher getMethodMatcher() {
+ return new StaticMethodMatcher() {
+ @Override
+ public boolean matches(Method method, Class> targetClass) {
+ return false;
+ }
+ };
+ }
+ });
+ delegates.add(new AuthorizationMethodAfterAdvice() {
+ @Override
+ public Object after(Supplier authentication, MethodAuthorizationContext object,
+ Object returnedObject) {
+ return returnedObject;
+ }
+
+ @Override
+ public MethodMatcher getMethodMatcher() {
+ return MethodMatcher.TRUE;
+ }
+ });
+ DelegatingAuthorizationMethodAfterAdvice advice = new DelegatingAuthorizationMethodAfterAdvice(delegates);
+ MethodMatcher methodMatcher = advice.getMethodMatcher();
+ assertThat(methodMatcher.matches(TestClass.class.getMethod("doSomething"), TestClass.class)).isTrue();
+ }
+
+ @Test
+ public void checkWhenDelegatingAdviceModifiesReturnedObjectThenModifiedReturnedObject() throws Exception {
+ List> delegates = new ArrayList<>();
+ delegates.add(new AuthorizationMethodAfterAdvice() {
+ @Override
+ public Object after(Supplier authentication, MethodAuthorizationContext object,
+ Object returnedObject) {
+ return returnedObject + "b";
+ }
+
+ @Override
+ public MethodMatcher getMethodMatcher() {
+ return MethodMatcher.TRUE;
+ }
+ });
+ delegates.add(new AuthorizationMethodAfterAdvice() {
+ @Override
+ public Object after(Supplier authentication, MethodAuthorizationContext object,
+ Object returnedObject) {
+ return returnedObject + "c";
+ }
+
+ @Override
+ public MethodMatcher getMethodMatcher() {
+ return MethodMatcher.TRUE;
+ }
+ });
+ MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+ "doSomething");
+ MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
+ TestClass.class);
+ DelegatingAuthorizationMethodAfterAdvice advice = new DelegatingAuthorizationMethodAfterAdvice(delegates);
+ Object result = advice.after(TestAuthentication::authenticatedUser, methodAuthorizationContext, "a");
+ assertThat(result).isEqualTo("abc");
+ }
+
+ public static class TestClass {
+
+ public String doSomething() {
+ return null;
+ }
+
+ }
+
+}
diff --git a/core/src/test/java/org/springframework/security/access/method/DelegatingAuthorizationMethodBeforeAdviceTests.java b/core/src/test/java/org/springframework/security/access/method/DelegatingAuthorizationMethodBeforeAdviceTests.java
new file mode 100644
index 00000000000..5e82783f7e2
--- /dev/null
+++ b/core/src/test/java/org/springframework/security/access/method/DelegatingAuthorizationMethodBeforeAdviceTests.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2002-2021 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.access.method;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Supplier;
+
+import org.junit.Test;
+
+import org.springframework.aop.MethodMatcher;
+import org.springframework.aop.support.StaticMethodMatcher;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.access.intercept.method.MockMethodInvocation;
+import org.springframework.security.authentication.TestAuthentication;
+import org.springframework.security.authorization.AuthorizationDecision;
+import org.springframework.security.core.Authentication;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+
+/**
+ * Tests for {@link DelegatingAuthorizationMethodBeforeAdvice}.
+ *
+ * @author Evgeniy Cheban
+ */
+public class DelegatingAuthorizationMethodBeforeAdviceTests {
+
+ @Test
+ public void methodMatcherWhenNoneMatchesThenNotMatches() throws Exception {
+ List> delegates = new ArrayList<>();
+ delegates.add(new AuthorizationMethodBeforeAdvice() {
+ @Override
+ public MethodMatcher getMethodMatcher() {
+ return new StaticMethodMatcher() {
+ @Override
+ public boolean matches(Method method, Class> targetClass) {
+ return false;
+ }
+ };
+ }
+
+ @Override
+ public void before(Supplier authentication, MethodAuthorizationContext object) {
+ }
+ });
+ delegates.add(new AuthorizationMethodBeforeAdvice() {
+ @Override
+ public MethodMatcher getMethodMatcher() {
+ return new StaticMethodMatcher() {
+ @Override
+ public boolean matches(Method method, Class> targetClass) {
+ return false;
+ }
+ };
+ }
+
+ @Override
+ public void before(Supplier authentication, MethodAuthorizationContext object) {
+ }
+ });
+ DelegatingAuthorizationMethodBeforeAdvice advice = new DelegatingAuthorizationMethodBeforeAdvice(delegates);
+ MethodMatcher methodMatcher = advice.getMethodMatcher();
+ assertThat(methodMatcher.matches(TestClass.class.getMethod("doSomething"), TestClass.class)).isFalse();
+ }
+
+ @Test
+ public void methodMatcherWhenAnyMatchesThenMatches() throws Exception {
+ List> delegates = new ArrayList<>();
+ delegates.add(new AuthorizationMethodBeforeAdvice() {
+ @Override
+ public MethodMatcher getMethodMatcher() {
+ return new StaticMethodMatcher() {
+ @Override
+ public boolean matches(Method method, Class> targetClass) {
+ return false;
+ }
+ };
+ }
+
+ @Override
+ public void before(Supplier authentication, MethodAuthorizationContext object) {
+ }
+ });
+ delegates.add(new AuthorizationMethodBeforeAdvice() {
+ @Override
+ public MethodMatcher getMethodMatcher() {
+ return MethodMatcher.TRUE;
+ }
+
+ @Override
+ public void before(Supplier authentication, MethodAuthorizationContext object) {
+ }
+ });
+ DelegatingAuthorizationMethodBeforeAdvice advice = new DelegatingAuthorizationMethodBeforeAdvice(delegates);
+ MethodMatcher methodMatcher = advice.getMethodMatcher();
+ assertThat(methodMatcher.matches(TestClass.class.getMethod("doSomething"), TestClass.class)).isTrue();
+ }
+
+ @Test
+ public void checkWhenAllGrantsOrAbstainsThenPasses() throws Exception {
+ List> delegates = new ArrayList<>();
+ delegates.add(new AuthorizationManagerMethodBeforeAdvice<>(MethodMatcher.TRUE, (a, o) -> null));
+ delegates.add(new AuthorizationManagerMethodBeforeAdvice<>(MethodMatcher.TRUE,
+ (a, o) -> new AuthorizationDecision(true)));
+ delegates.add(new AuthorizationManagerMethodBeforeAdvice<>(MethodMatcher.TRUE, (a, o) -> null));
+ DelegatingAuthorizationMethodBeforeAdvice advice = new DelegatingAuthorizationMethodBeforeAdvice(delegates);
+ MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+ "doSomething");
+ MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
+ TestClass.class);
+ advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext);
+ }
+
+ @Test
+ public void checkWhenAnyDeniesThenAccessDeniedException() throws Exception {
+ List> delegates = new ArrayList<>();
+ delegates.add(new AuthorizationManagerMethodBeforeAdvice<>(MethodMatcher.TRUE, (a, o) -> null));
+ delegates.add(new AuthorizationManagerMethodBeforeAdvice<>(MethodMatcher.TRUE,
+ (a, o) -> new AuthorizationDecision(true)));
+ delegates.add(new AuthorizationManagerMethodBeforeAdvice<>(MethodMatcher.TRUE,
+ (a, o) -> new AuthorizationDecision(false)));
+ DelegatingAuthorizationMethodBeforeAdvice advice = new DelegatingAuthorizationMethodBeforeAdvice(delegates);
+ MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+ "doSomething");
+ MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
+ TestClass.class);
+ assertThatExceptionOfType(AccessDeniedException.class)
+ .isThrownBy(() -> advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext))
+ .withMessage("Access Denied");
+ }
+
+ @Test
+ public void checkWhenDelegatesEmptyThenPasses() throws Exception {
+ DelegatingAuthorizationMethodBeforeAdvice advice = new DelegatingAuthorizationMethodBeforeAdvice(
+ Collections.emptyList());
+ MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+ "doSomething");
+ MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
+ TestClass.class);
+ advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext);
+ }
+
+ public static class TestClass {
+
+ public void doSomething() {
+
+ }
+
+ }
+
+}
diff --git a/core/src/test/java/org/springframework/security/authorization/AuthorityAuthorizationManagerTests.java b/core/src/test/java/org/springframework/security/authorization/AuthorityAuthorizationManagerTests.java
index ab0c41563c7..5c092d54d43 100644
--- a/core/src/test/java/org/springframework/security/authorization/AuthorityAuthorizationManagerTests.java
+++ b/core/src/test/java/org/springframework/security/authorization/AuthorityAuthorizationManagerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2021 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.
@@ -64,6 +64,13 @@ public void hasAnyRoleWhenContainNullThenException() {
.withMessage("roles cannot contain null values");
}
+ @Test
+ public void hasAnyRoleWhenCustomRolePrefixNullThenException() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> AuthorityAuthorizationManager.hasAnyRole(null, new String[] { "ADMIN", "USER" }))
+ .withMessage("rolePrefix cannot be null");
+ }
+
@Test
public void hasAnyAuthorityWhenNullThenException() {
assertThatIllegalArgumentException().isThrownBy(() -> AuthorityAuthorizationManager.hasAnyAuthority(null))
@@ -147,6 +154,17 @@ public void hasAnyRoleWhenUserHasNotAnyRoleThenDeniedDecision() {
assertThat(manager.check(authentication, object).isGranted()).isFalse();
}
+ @Test
+ public void hasAnyRoleWhenCustomRolePrefixProvidedThenUseCustomRolePrefix() {
+ AuthorityAuthorizationManager