Преглед изворни кода

Consider AuthorizationManager for Method Security

Closes gh-9289
Evgeniy Cheban пре 4 година
родитељ
комит
20778f727b
35 измењених фајлова са 3682 додато и 5 уклоњено
  1. 1 0
      config/spring-security-config.gradle
  2. 87 0
      config/src/main/java/org/springframework/security/config/annotation/method/configuration/EnableMethodSecurity.java
  3. 252 0
      config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityConfiguration.java
  4. 50 0
      config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecuritySelector.java
  5. 360 0
      config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityConfigurationTests.java
  6. 67 0
      core/src/main/java/org/springframework/security/access/annotation/AbstractAuthorizationManagerRegistry.java
  7. 123 0
      core/src/main/java/org/springframework/security/access/annotation/Jsr250AuthorizationManager.java
  8. 80 0
      core/src/main/java/org/springframework/security/access/annotation/SecuredAuthorizationManager.java
  9. 82 0
      core/src/main/java/org/springframework/security/access/intercept/aopalliance/AuthorizationMethodInterceptor.java
  10. 73 0
      core/src/main/java/org/springframework/security/access/method/AuthorizationManagerMethodAfterAdvice.java
  11. 72 0
      core/src/main/java/org/springframework/security/access/method/AuthorizationManagerMethodBeforeAdvice.java
  12. 61 0
      core/src/main/java/org/springframework/security/access/method/AuthorizationMethodAfterAdvice.java
  13. 52 0
      core/src/main/java/org/springframework/security/access/method/AuthorizationMethodBeforeAdvice.java
  14. 102 0
      core/src/main/java/org/springframework/security/access/method/DelegatingAuthorizationMethodAfterAdvice.java
  15. 95 0
      core/src/main/java/org/springframework/security/access/method/DelegatingAuthorizationMethodBeforeAdvice.java
  16. 84 0
      core/src/main/java/org/springframework/security/access/method/MethodAuthorizationContext.java
  17. 17 4
      core/src/main/java/org/springframework/security/authorization/AuthorityAuthorizationManager.java
  18. 73 0
      core/src/main/java/org/springframework/security/authorization/method/AbstractExpressionAttributeRegistry.java
  19. 52 0
      core/src/main/java/org/springframework/security/authorization/method/ExpressionAttribute.java
  20. 108 0
      core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java
  21. 126 0
      core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodAfterAdvice.java
  22. 107 0
      core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java
  23. 151 0
      core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodBeforeAdvice.java
  24. 167 0
      core/src/test/java/org/springframework/security/access/annotation/Jsr250AuthorizationManagerTests.java
  25. 104 0
      core/src/test/java/org/springframework/security/access/annotation/SecuredAuthorizationManagerTests.java
  26. 107 0
      core/src/test/java/org/springframework/security/access/intercept/aopalliance/AuthorizationMethodInterceptorTests.java
  27. 68 0
      core/src/test/java/org/springframework/security/access/method/AuthorizationManagerMethodAfterAdviceTests.java
  28. 65 0
      core/src/test/java/org/springframework/security/access/method/AuthorizationManagerMethodBeforeAdviceTests.java
  29. 164 0
      core/src/test/java/org/springframework/security/access/method/DelegatingAuthorizationMethodAfterAdviceTests.java
  30. 168 0
      core/src/test/java/org/springframework/security/access/method/DelegatingAuthorizationMethodBeforeAdviceTests.java
  31. 19 1
      core/src/test/java/org/springframework/security/authorization/AuthorityAuthorizationManagerTests.java
  32. 144 0
      core/src/test/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManagerTests.java
  33. 96 0
      core/src/test/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodAfterAdviceTests.java
  34. 105 0
      core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManagerTests.java
  35. 200 0
      core/src/test/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodBeforeAdviceTests.java

+ 1 - 0
config/spring-security-config.gradle

@@ -38,6 +38,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'
 

+ 87 - 0
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}. <strong>
+	 * Applicable only if {@link #mode()} is set to {@link AdviceMode#PROXY}</strong>.
+	 * <p>
+	 * Note that setting this attribute to {@code true} will affect <em>all</em>
+	 * 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;
+
+}

+ 252 - 0
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<MethodAuthorizationContext> authorizationMethodBeforeAdvice;
+
+	private AuthorizationMethodAfterAdvice<MethodAuthorizationContext> 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<MethodAuthorizationContext> getAuthorizationMethodBeforeAdvice() {
+		if (this.authorizationMethodBeforeAdvice == null) {
+			this.authorizationMethodBeforeAdvice = createDefaultAuthorizationMethodBeforeAdvice();
+		}
+		return this.authorizationMethodBeforeAdvice;
+	}
+
+	private AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> createDefaultAuthorizationMethodBeforeAdvice() {
+		List<AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>> 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<MethodAuthorizationContext> getPreAuthorizeAuthorizationMethodBeforeAdvice() {
+		MethodMatcher methodMatcher = new SecurityAnnotationsStaticMethodMatcher(PreAuthorize.class);
+		PreAuthorizeAuthorizationManager authorizationManager = new PreAuthorizeAuthorizationManager();
+		authorizationManager.setExpressionHandler(getMethodSecurityExpressionHandler());
+		return new AuthorizationManagerMethodBeforeAdvice<>(methodMatcher, authorizationManager);
+	}
+
+	private AuthorizationManagerMethodBeforeAdvice<MethodAuthorizationContext> getSecuredAuthorizationMethodBeforeAdvice() {
+		MethodMatcher methodMatcher = new SecurityAnnotationsStaticMethodMatcher(Secured.class);
+		SecuredAuthorizationManager authorizationManager = new SecuredAuthorizationManager();
+		return new AuthorizationManagerMethodBeforeAdvice<>(methodMatcher, authorizationManager);
+	}
+
+	private AuthorizationManagerMethodBeforeAdvice<MethodAuthorizationContext> 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<MethodAuthorizationContext> authorizationMethodBeforeAdvice) {
+		this.authorizationMethodBeforeAdvice = authorizationMethodBeforeAdvice;
+	}
+
+	private AuthorizationMethodAfterAdvice<MethodAuthorizationContext> getAuthorizationMethodAfterAdvice() {
+		if (this.authorizationMethodAfterAdvice == null) {
+			this.authorizationMethodAfterAdvice = createDefaultAuthorizationMethodAfterAdvice();
+		}
+		return this.authorizationMethodAfterAdvice;
+	}
+
+	private AuthorizationMethodAfterAdvice<MethodAuthorizationContext> createDefaultAuthorizationMethodAfterAdvice() {
+		List<AuthorizationMethodAfterAdvice<MethodAuthorizationContext>> 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<MethodAuthorizationContext> 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<MethodAuthorizationContext> authorizationMethodAfterAdvice) {
+		this.authorizationMethodAfterAdvice = authorizationMethodAfterAdvice;
+	}
+
+	@Override
+	public void setImportMetadata(AnnotationMetadata importMetadata) {
+		Map<String, Object> 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<Class<? extends Annotation>> 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<Annotation> annotations = AnnotatedElementUtils.findAllMergedAnnotations(annotatedElement,
+					this.annotationClasses);
+			return !annotations.isEmpty();
+		}
+
+	}
+
+}

+ 50 - 0
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<EnableMethodSecurity> {
+
+	@Override
+	protected String[] selectImports(AdviceMode adviceMode) {
+		if (adviceMode == AdviceMode.PROXY) {
+			return getProxyImports();
+		}
+		throw new IllegalStateException("AdviceMode '" + adviceMode + "' is not supported");
+	}
+
+	private String[] getProxyImports() {
+		List<String> result = new ArrayList<>();
+		result.add(AutoProxyRegistrar.class.getName());
+		result.add(MethodSecurityConfiguration.class.getName());
+		return result.toArray(new String[0]);
+	}
+
+}

+ 360 - 0
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<String> 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<String> 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<MethodAuthorizationContext> customBeforeAdvice() {
+			JdkRegexpMethodPointcut methodMatcher = new JdkRegexpMethodPointcut();
+			methodMatcher.setPattern(".*MethodSecurityServiceImpl.*securedUser");
+			AuthorizationManager<MethodAuthorizationContext> authorizationManager = (a,
+					o) -> new AuthorizationDecision("bob".equals(a.get().getName()));
+			return new AuthorizationManagerMethodBeforeAdvice<>(methodMatcher, authorizationManager);
+		}
+
+	}
+
+	@EnableMethodSecurity
+	static class CustomAuthorizationManagerAfterAdviceConfig {
+
+		@Bean
+		AuthorizationMethodAfterAdvice<MethodAuthorizationContext> customAfterAdvice() {
+			JdkRegexpMethodPointcut methodMatcher = new JdkRegexpMethodPointcut();
+			methodMatcher.setPattern(".*MethodSecurityServiceImpl.*securedUser");
+			return new AuthorizationMethodAfterAdvice<MethodAuthorizationContext>() {
+				@Override
+				public MethodMatcher getMethodMatcher() {
+					return methodMatcher;
+				}
+
+				@Override
+				public Object after(Supplier<Authentication> 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() + "'");
+				}
+			};
+		}
+
+	}
+
+}

+ 67 - 0
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<MethodAuthorizationContext> NULL_MANAGER = (a, o) -> null;
+
+	private final Map<MethodClassKey, AuthorizationManager<MethodAuthorizationContext>> 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<MethodAuthorizationContext> 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<MethodAuthorizationContext> resolveManager(Method method, Class<?> targetClass);
+
+}

+ 123 - 0
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<MethodAuthorizationContext> {
+
+	private static final Set<Class<? extends Annotation>> 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> authentication,
+			MethodAuthorizationContext methodAuthorizationContext) {
+		AuthorizationManager<MethodAuthorizationContext> delegate = this.registry
+				.getManager(methodAuthorizationContext);
+		return delegate.check(authentication, methodAuthorizationContext);
+	}
+
+	private final class Jsr250AuthorizationManagerRegistry extends AbstractAuthorizationManagerRegistry {
+
+		@NonNull
+		@Override
+		AuthorizationManager<MethodAuthorizationContext> 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<Annotation> findJsr250Annotations(Method method, Class<?> targetClass) {
+			Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
+			Set<Annotation> annotations = findAnnotations(specificMethod);
+			return (annotations.isEmpty()) ? findAnnotations(specificMethod.getDeclaringClass()) : annotations;
+		}
+
+		private Set<Annotation> findAnnotations(AnnotatedElement annotatedElement) {
+			return AnnotatedElementUtils.findAllMergedAnnotations(annotatedElement, JSR250_ANNOTATIONS);
+		}
+
+	}
+
+}

+ 80 - 0
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<MethodAuthorizationContext> {
+
+	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> authentication,
+			MethodAuthorizationContext methodAuthorizationContext) {
+		AuthorizationManager<MethodAuthorizationContext> delegate = this.registry
+				.getManager(methodAuthorizationContext);
+		return delegate.check(authentication, methodAuthorizationContext);
+	}
+
+	private static final class SecuredAuthorizationManagerRegistry extends AbstractAuthorizationManagerRegistry {
+
+		@NonNull
+		@Override
+		AuthorizationManager<MethodAuthorizationContext> 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);
+		}
+
+	}
+
+}

+ 82 - 0
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<MethodAuthorizationContext> beforeAdvice;
+
+	private final AuthorizationMethodAfterAdvice<MethodAuthorizationContext> afterAdvice;
+
+	/**
+	 * Creates an instance.
+	 * @param beforeAdvice the {@link AuthorizationMethodBeforeAdvice} to use
+	 * @param afterAdvice the {@link AuthorizationMethodAfterAdvice} to use
+	 */
+	public AuthorizationMethodInterceptor(AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> beforeAdvice,
+			AuthorizationMethodAfterAdvice<MethodAuthorizationContext> 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;
+	}
+
+}

+ 73 - 0
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 <T> the type of object that the authorization check is being done one.
+ * @author Evgeniy Cheban
+ * @since 5.5
+ */
+public final class AuthorizationManagerMethodAfterAdvice<T> implements AuthorizationMethodAfterAdvice<T> {
+
+	private final MethodMatcher methodMatcher;
+
+	private final AuthorizationManager<T> authorizationManager;
+
+	/**
+	 * Creates an instance.
+	 * @param methodMatcher the {@link MethodMatcher} to use
+	 * @param authorizationManager the {@link AuthorizationManager} to use
+	 */
+	public AuthorizationManagerMethodAfterAdvice(MethodMatcher methodMatcher,
+			AuthorizationManager<T> 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> authentication, T object, Object returnedObject) {
+		this.authorizationManager.verify(authentication, object);
+		return returnedObject;
+	}
+
+	@Override
+	public MethodMatcher getMethodMatcher() {
+		return this.methodMatcher;
+	}
+
+}

+ 72 - 0
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 <T> the type of object that the authorization check is being done one.
+ * @author Evgeniy Cheban
+ * @since 5.5
+ */
+public final class AuthorizationManagerMethodBeforeAdvice<T> implements AuthorizationMethodBeforeAdvice<T> {
+
+	private final MethodMatcher methodMatcher;
+
+	private final AuthorizationManager<T> authorizationManager;
+
+	/**
+	 * Creates an instance.
+	 * @param methodMatcher the {@link MethodMatcher} to use
+	 * @param authorizationManager the {@link AuthorizationManager} to use
+	 */
+	public AuthorizationManagerMethodBeforeAdvice(MethodMatcher methodMatcher,
+			AuthorizationManager<T> 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> authentication, T object) {
+		this.authorizationManager.verify(authentication, object);
+	}
+
+	@Override
+	public MethodMatcher getMethodMatcher() {
+		return this.methodMatcher;
+	}
+
+}

+ 61 - 0
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 <T> the type of object that the authorization check is being done one.
+ * @author Evgeniy Cheban
+ * @since 5.5
+ */
+public interface AuthorizationMethodAfterAdvice<T> 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 <code>Object</code> 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
+	 * <code>returnedObject</code> method argument)
+	 */
+	Object after(Supplier<Authentication> authentication, T object, Object returnedObject);
+
+}

+ 52 - 0
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 <T> the type of object that the authorization check is being done one.
+ * @author Evgeniy Cheban
+ * @since 5.5
+ */
+public interface AuthorizationMethodBeforeAdvice<T> 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> authentication, T object);
+
+}

+ 102 - 0
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<MethodAuthorizationContext> {
+
+	private final Log logger = LogFactory.getLog(getClass());
+
+	private final MethodMatcher methodMatcher = new StaticMethodMatcher() {
+		@Override
+		public boolean matches(Method method, Class<?> targetClass) {
+			for (AuthorizationMethodAfterAdvice<MethodAuthorizationContext> delegate : DelegatingAuthorizationMethodAfterAdvice.this.delegates) {
+				MethodMatcher methodMatcher = delegate.getMethodMatcher();
+				if (methodMatcher.matches(method, targetClass)) {
+					return true;
+				}
+			}
+			return false;
+		}
+	};
+
+	private final List<AuthorizationMethodAfterAdvice<MethodAuthorizationContext>> delegates;
+
+	/**
+	 * Creates an instance.
+	 * @param delegates the {@link AuthorizationMethodAfterAdvice}s to use
+	 */
+	public DelegatingAuthorizationMethodAfterAdvice(
+			List<AuthorizationMethodAfterAdvice<MethodAuthorizationContext>> delegates) {
+		this.delegates = delegates;
+	}
+
+	@Override
+	public MethodMatcher getMethodMatcher() {
+		return this.methodMatcher;
+	}
+
+	/**
+	 * Delegates to specific {@link AuthorizationMethodAfterAdvice}s and returns the
+	 * <code>returnedObject</code> (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 <code>returnedObject</code> (possibly modified) from the method
+	 * argument
+	 */
+	@Override
+	public Object after(Supplier<Authentication> 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<MethodAuthorizationContext> 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;
+	}
+
+}

+ 95 - 0
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<MethodAuthorizationContext> {
+
+	private final Log logger = LogFactory.getLog(getClass());
+
+	private final MethodMatcher methodMatcher = new StaticMethodMatcher() {
+		@Override
+		public boolean matches(Method method, Class<?> targetClass) {
+			for (AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> delegate : DelegatingAuthorizationMethodBeforeAdvice.this.delegates) {
+				MethodMatcher methodMatcher = delegate.getMethodMatcher();
+				if (methodMatcher.matches(method, targetClass)) {
+					return true;
+				}
+			}
+			return false;
+		}
+	};
+
+	private final List<AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>> delegates;
+
+	/**
+	 * Creates an instance.
+	 * @param delegates the {@link AuthorizationMethodBeforeAdvice}s to use
+	 */
+	public DelegatingAuthorizationMethodBeforeAdvice(
+			List<AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>> 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> authentication, MethodAuthorizationContext methodAuthorizationContext) {
+		if (this.logger.isTraceEnabled()) {
+			this.logger.trace(LogMessage.format("Pre Authorizing %s", methodAuthorizationContext));
+		}
+		for (AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> delegate : this.delegates) {
+			if (this.logger.isTraceEnabled()) {
+				this.logger.trace(LogMessage.format("Checking authorization on %s using %s", methodAuthorizationContext,
+						delegate));
+			}
+			delegate.before(authentication, methodAuthorizationContext);
+		}
+	}
+
+}

+ 84 - 0
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 + ']';
+	}
+
+}

+ 17 - 4
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 final class AuthorityAuthorizationManager<T> implements AuthorizationMana
 	 * @return the new instance
 	 */
 	public static <T> AuthorityAuthorizationManager<T> hasAnyRole(String... roles) {
+		return hasAnyRole(ROLE_PREFIX, roles);
+	}
+
+	/**
+	 * Creates an instance of {@link AuthorityAuthorizationManager} with the provided
+	 * authorities.
+	 * @param rolePrefix the role prefix for <code>roles</code>
+	 * @param roles the authorities to check for prefixed with <code>rolePrefix</code>
+	 * @param <T> the type of object being authorized
+	 * @return the new instance
+	 */
+	public static <T> AuthorityAuthorizationManager<T> 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 final class AuthorityAuthorizationManager<T> implements AuthorizationMana
 		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;
 	}

+ 73 - 0
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<T extends ExpressionAttribute> {
+
+	private final Map<MethodClassKey, T> 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);
+
+}

+ 52 - 0
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;
+	}
+
+}

+ 108 - 0
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<MethodAuthorizationContext> {
+
+	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> 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<ExpressionAttribute> {
+
+		@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);
+		}
+
+	}
+
+}

+ 126 - 0
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 <code>returnedObject</code>
+ * 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<MethodAuthorizationContext> {
+
+	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 <code>returnedObject</code> 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 <code>returnedObject</code> from the {@link MethodInvocation}
+	 */
+	@Override
+	public Object after(Supplier<Authentication> 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<ExpressionAttribute> {
+
+		@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);
+		}
+
+	}
+
+}

+ 107 - 0
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<MethodAuthorizationContext> {
+
+	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> 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<ExpressionAttribute> {
+
+		@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);
+		}
+
+	}
+
+}

+ 151 - 0
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<MethodAuthorizationContext> {
+
+	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> 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<PreFilterExpressionAttribute> {
+
+		@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;
+		}
+
+	}
+
+}

+ 167 - 0
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> 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() {
+
+		}
+
+	}
+
+}

+ 104 - 0
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> 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() {
+
+		}
+
+	}
+
+}

+ 107 - 0
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<MethodAuthorizationContext> mockBeforeAdvice = mock(
+				AuthorizationMethodBeforeAdvice.class);
+		AuthorizationMethodAfterAdvice<MethodAuthorizationContext> 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<MethodAuthorizationContext> beforeAdvice = new AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>() {
+			@Override
+			public MethodMatcher getMethodMatcher() {
+				return MethodMatcher.TRUE;
+			}
+
+			@Override
+			public void before(Supplier<Authentication> authentication,
+					MethodAuthorizationContext methodAuthorizationContext) {
+				authentication.get();
+			}
+		};
+		AuthorizationMethodAfterAdvice<MethodAuthorizationContext> 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;
+		}
+
+	}
+
+}

+ 68 - 0
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> authentication = TestAuthentication::authenticatedUser;
+		MethodInvocation mockMethodInvocation = mock(MethodInvocation.class);
+		Object returnedObject = new Object();
+		AuthorizationManager<MethodInvocation> mockAuthorizationManager = mock(AuthorizationManager.class);
+		AuthorizationManagerMethodAfterAdvice<MethodInvocation> advice = new AuthorizationManagerMethodAfterAdvice<>(
+				mock(MethodMatcher.class), mockAuthorizationManager);
+		Object result = advice.after(authentication, mockMethodInvocation, returnedObject);
+		assertThat(result).isEqualTo(returnedObject);
+		verify(mockAuthorizationManager).verify(authentication, mockMethodInvocation);
+	}
+
+}

+ 65 - 0
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> authentication = TestAuthentication::authenticatedUser;
+		MethodInvocation mockMethodInvocation = mock(MethodInvocation.class);
+		AuthorizationManager<MethodInvocation> mockAuthorizationManager = mock(AuthorizationManager.class);
+		AuthorizationManagerMethodBeforeAdvice<MethodInvocation> advice = new AuthorizationManagerMethodBeforeAdvice<>(
+				mock(MethodMatcher.class), mockAuthorizationManager);
+		advice.before(authentication, mockMethodInvocation);
+		verify(mockAuthorizationManager).verify(authentication, mockMethodInvocation);
+	}
+
+}

+ 164 - 0
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<AuthorizationMethodAfterAdvice<MethodAuthorizationContext>> delegates = new ArrayList<>();
+		delegates.add(new AuthorizationMethodAfterAdvice<MethodAuthorizationContext>() {
+			@Override
+			public Object after(Supplier<Authentication> 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<MethodAuthorizationContext>() {
+			@Override
+			public Object after(Supplier<Authentication> 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<AuthorizationMethodAfterAdvice<MethodAuthorizationContext>> delegates = new ArrayList<>();
+		delegates.add(new AuthorizationMethodAfterAdvice<MethodAuthorizationContext>() {
+			@Override
+			public Object after(Supplier<Authentication> 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<MethodAuthorizationContext>() {
+			@Override
+			public Object after(Supplier<Authentication> 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<AuthorizationMethodAfterAdvice<MethodAuthorizationContext>> delegates = new ArrayList<>();
+		delegates.add(new AuthorizationMethodAfterAdvice<MethodAuthorizationContext>() {
+			@Override
+			public Object after(Supplier<Authentication> authentication, MethodAuthorizationContext object,
+					Object returnedObject) {
+				return returnedObject + "b";
+			}
+
+			@Override
+			public MethodMatcher getMethodMatcher() {
+				return MethodMatcher.TRUE;
+			}
+		});
+		delegates.add(new AuthorizationMethodAfterAdvice<MethodAuthorizationContext>() {
+			@Override
+			public Object after(Supplier<Authentication> 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;
+		}
+
+	}
+
+}

+ 168 - 0
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<AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>> delegates = new ArrayList<>();
+		delegates.add(new AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>() {
+			@Override
+			public MethodMatcher getMethodMatcher() {
+				return new StaticMethodMatcher() {
+					@Override
+					public boolean matches(Method method, Class<?> targetClass) {
+						return false;
+					}
+				};
+			}
+
+			@Override
+			public void before(Supplier<Authentication> authentication, MethodAuthorizationContext object) {
+			}
+		});
+		delegates.add(new AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>() {
+			@Override
+			public MethodMatcher getMethodMatcher() {
+				return new StaticMethodMatcher() {
+					@Override
+					public boolean matches(Method method, Class<?> targetClass) {
+						return false;
+					}
+				};
+			}
+
+			@Override
+			public void before(Supplier<Authentication> 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<AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>> delegates = new ArrayList<>();
+		delegates.add(new AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>() {
+			@Override
+			public MethodMatcher getMethodMatcher() {
+				return new StaticMethodMatcher() {
+					@Override
+					public boolean matches(Method method, Class<?> targetClass) {
+						return false;
+					}
+				};
+			}
+
+			@Override
+			public void before(Supplier<Authentication> authentication, MethodAuthorizationContext object) {
+			}
+		});
+		delegates.add(new AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>() {
+			@Override
+			public MethodMatcher getMethodMatcher() {
+				return MethodMatcher.TRUE;
+			}
+
+			@Override
+			public void before(Supplier<Authentication> 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<AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>> 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<AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>> 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() {
+
+		}
+
+	}
+
+}

+ 19 - 1
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 class AuthorityAuthorizationManagerTests {
 				.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 class AuthorityAuthorizationManagerTests {
 		assertThat(manager.check(authentication, object).isGranted()).isFalse();
 	}
 
+	@Test
+	public void hasAnyRoleWhenCustomRolePrefixProvidedThenUseCustomRolePrefix() {
+		AuthorityAuthorizationManager<Object> manager = AuthorityAuthorizationManager.hasAnyRole("CUSTOM_",
+				new String[] { "USER" });
+		Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password",
+				"CUSTOM_USER");
+		Object object = new Object();
+
+		assertThat(manager.check(authentication, object).isGranted()).isTrue();
+	}
+
 	@Test
 	public void hasAnyAuthorityWhenUserHasAnyAuthorityThenGrantedDecision() {
 		AuthorityAuthorizationManager<Object> manager = AuthorityAuthorizationManager.hasAnyAuthority("ADMIN", "USER");

+ 144 - 0
core/src/test/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManagerTests.java

@@ -0,0 +1,144 @@
+/*
+ * 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.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Test;
+
+import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
+import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
+import org.springframework.security.access.intercept.method.MockMethodInvocation;
+import org.springframework.security.access.method.MethodAuthorizationContext;
+import org.springframework.security.access.prepost.PostAuthorize;
+import org.springframework.security.authentication.TestAuthentication;
+import org.springframework.security.authorization.AuthorizationDecision;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+
+/**
+ * Tests for {@link PostAuthorizeAuthorizationManager}.
+ *
+ * @author Evgeniy Cheban
+ */
+public class PostAuthorizeAuthorizationManagerTests {
+
+	@Test
+	public void setExpressionHandlerWhenNotNullThenSetsExpressionHandler() {
+		MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
+		PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
+		manager.setExpressionHandler(expressionHandler);
+		assertThat(manager).extracting("expressionHandler").isEqualTo(expressionHandler);
+	}
+
+	@Test
+	public void setExpressionHandlerWhenNullThenException() {
+		PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
+		assertThatIllegalArgumentException().isThrownBy(() -> manager.setExpressionHandler(null))
+				.withMessage("expressionHandler cannot be null");
+	}
+
+	@Test
+	public void checkDoSomethingWhenNoPostAuthorizeAnnotationThenNullDecision() throws Exception {
+		MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+				"doSomething", new Class[] {}, new Object[] {});
+		MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
+				TestClass.class);
+		PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
+		AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
+				methodAuthorizationContext);
+		assertThat(decision).isNull();
+	}
+
+	@Test
+	public void checkDoSomethingStringWhenArgIsGrantThenGrantedDecision() throws Exception {
+		MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+				"doSomethingString", new Class[] { String.class }, new Object[] { "grant" });
+		MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
+				TestClass.class);
+		PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
+		AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
+				methodAuthorizationContext);
+		assertThat(decision).isNotNull();
+		assertThat(decision.isGranted()).isTrue();
+	}
+
+	@Test
+	public void checkDoSomethingStringWhenArgIsNotGrantThenDeniedDecision() throws Exception {
+		MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+				"doSomethingString", new Class[] { String.class }, new Object[] { "deny" });
+		MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
+				TestClass.class);
+		PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
+		AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
+				methodAuthorizationContext);
+		assertThat(decision).isNotNull();
+		assertThat(decision.isGranted()).isFalse();
+	}
+
+	@Test
+	public void checkDoSomethingListWhenReturnObjectContainsGrantThenGrantedDecision() throws Exception {
+		List<String> list = Arrays.asList("grant", "deny");
+		MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+				"doSomethingList", new Class[] { List.class }, new Object[] { list });
+		MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
+				TestClass.class);
+		methodAuthorizationContext.setReturnObject(list);
+		PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
+		AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
+				methodAuthorizationContext);
+		assertThat(decision).isNotNull();
+		assertThat(decision.isGranted()).isTrue();
+	}
+
+	@Test
+	public void checkDoSomethingListWhenReturnObjectNotContainsGrantThenDeniedDecision() throws Exception {
+		List<String> list = Collections.singletonList("deny");
+		MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+				"doSomethingList", new Class[] { List.class }, new Object[] { list });
+		MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
+				TestClass.class);
+		methodAuthorizationContext.setReturnObject(list);
+		PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
+		AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
+				methodAuthorizationContext);
+		assertThat(decision).isNotNull();
+		assertThat(decision.isGranted()).isFalse();
+	}
+
+	public static class TestClass {
+
+		public void doSomething() {
+
+		}
+
+		@PostAuthorize("#s == 'grant'")
+		public String doSomethingString(String s) {
+			return s;
+		}
+
+		@PostAuthorize("returnObject.contains('grant')")
+		public List<String> doSomethingList(List<String> list) {
+			return list;
+		}
+
+	}
+
+}

+ 96 - 0
core/src/test/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodAfterAdviceTests.java

@@ -0,0 +1,96 @@
+/*
+ * 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.assertj.core.api.InstanceOfAssertFactories;
+import org.junit.Test;
+
+import org.springframework.aop.MethodMatcher;
+import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
+import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
+import org.springframework.security.access.intercept.method.MockMethodInvocation;
+import org.springframework.security.access.method.MethodAuthorizationContext;
+import org.springframework.security.access.prepost.PostFilter;
+import org.springframework.security.authentication.TestAuthentication;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+
+/**
+ * Tests for {@link PostFilterAuthorizationMethodAfterAdvice}.
+ *
+ * @author Evgeniy Cheban
+ */
+public class PostFilterAuthorizationMethodAfterAdviceTests {
+
+	@Test
+	public void setExpressionHandlerWhenNotNullThenSetsExpressionHandler() {
+		MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
+		PostFilterAuthorizationMethodAfterAdvice advice = new PostFilterAuthorizationMethodAfterAdvice();
+		advice.setExpressionHandler(expressionHandler);
+		assertThat(advice).extracting("expressionHandler").isEqualTo(expressionHandler);
+	}
+
+	@Test
+	public void setExpressionHandlerWhenNullThenException() {
+		PostFilterAuthorizationMethodAfterAdvice advice = new PostFilterAuthorizationMethodAfterAdvice();
+		assertThatIllegalArgumentException().isThrownBy(() -> advice.setExpressionHandler(null))
+				.withMessage("expressionHandler cannot be null");
+	}
+
+	@Test
+	public void methodMatcherWhenMethodHasNotPostFilterAnnotationThenNotMatches() throws Exception {
+		PostFilterAuthorizationMethodAfterAdvice advice = new PostFilterAuthorizationMethodAfterAdvice();
+		MethodMatcher methodMatcher = advice.getMethodMatcher();
+		assertThat(methodMatcher.matches(TestClass.class.getMethod("doSomething"), TestClass.class)).isFalse();
+	}
+
+	@Test
+	public void methodMatcherWhenMethodHasPostFilterAnnotationThenMatches() throws Exception {
+		PostFilterAuthorizationMethodAfterAdvice advice = new PostFilterAuthorizationMethodAfterAdvice();
+		MethodMatcher methodMatcher = advice.getMethodMatcher();
+		assertThat(
+				methodMatcher.matches(TestClass.class.getMethod("doSomethingArray", String[].class), TestClass.class))
+						.isTrue();
+	}
+
+	@Test
+	public void afterWhenArrayNotNullThenFilteredArray() throws Exception {
+		String[] array = { "john", "bob" };
+		MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+				"doSomethingArray", new Class[] { String[].class }, new Object[] { array });
+		MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
+				TestClass.class);
+		PostFilterAuthorizationMethodAfterAdvice advice = new PostFilterAuthorizationMethodAfterAdvice();
+		Object result = advice.after(TestAuthentication::authenticatedUser, methodAuthorizationContext, array);
+		assertThat(result).asInstanceOf(InstanceOfAssertFactories.array(String[].class)).containsOnly("john");
+	}
+
+	public static class TestClass {
+
+		public void doSomething() {
+
+		}
+
+		@PostFilter("filterObject == 'john'")
+		public String[] doSomethingArray(String[] array) {
+			return array;
+		}
+
+	}
+
+}

+ 105 - 0
core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManagerTests.java

@@ -0,0 +1,105 @@
+/*
+ * 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.junit.Test;
+
+import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
+import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
+import org.springframework.security.access.intercept.method.MockMethodInvocation;
+import org.springframework.security.access.method.MethodAuthorizationContext;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.authentication.TestAuthentication;
+import org.springframework.security.authorization.AuthorizationDecision;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+
+/**
+ * Tests for {@link PreAuthorizeAuthorizationManager}.
+ *
+ * @author Evgeniy Cheban
+ */
+public class PreAuthorizeAuthorizationManagerTests {
+
+	@Test
+	public void setExpressionHandlerWhenNotNullThenSetsExpressionHandler() {
+		MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
+		PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
+		manager.setExpressionHandler(expressionHandler);
+		assertThat(manager).extracting("expressionHandler").isEqualTo(expressionHandler);
+	}
+
+	@Test
+	public void setExpressionHandlerWhenNullThenException() {
+		PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
+		assertThatIllegalArgumentException().isThrownBy(() -> manager.setExpressionHandler(null))
+				.withMessage("expressionHandler cannot be null");
+	}
+
+	@Test
+	public void checkDoSomethingWhenNoPostAuthorizeAnnotationThenNullDecision() throws Exception {
+		MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+				"doSomething", new Class[] {}, new Object[] {});
+		MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
+				TestClass.class);
+		PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
+		AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
+				methodAuthorizationContext);
+		assertThat(decision).isNull();
+	}
+
+	@Test
+	public void checkDoSomethingStringWhenArgIsGrantThenGrantedDecision() throws Exception {
+		MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+				"doSomethingString", new Class[] { String.class }, new Object[] { "grant" });
+		MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
+				TestClass.class);
+		PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
+		AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
+				methodAuthorizationContext);
+		assertThat(decision).isNotNull();
+		assertThat(decision.isGranted()).isTrue();
+	}
+
+	@Test
+	public void checkDoSomethingStringWhenArgIsNotGrantThenDeniedDecision() throws Exception {
+		MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+				"doSomethingString", new Class[] { String.class }, new Object[] { "deny" });
+		MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
+				TestClass.class);
+		PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
+		AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
+				methodAuthorizationContext);
+		assertThat(decision).isNotNull();
+		assertThat(decision.isGranted()).isFalse();
+	}
+
+	public static class TestClass {
+
+		public void doSomething() {
+
+		}
+
+		@PreAuthorize("#s == 'grant'")
+		public String doSomethingString(String s) {
+			return s;
+		}
+
+	}
+
+}

+ 200 - 0
core/src/test/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodBeforeAdviceTests.java

@@ -0,0 +1,200 @@
+/*
+ * 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.util.ArrayList;
+import java.util.List;
+
+import org.junit.Test;
+
+import org.springframework.aop.MethodMatcher;
+import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
+import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
+import org.springframework.security.access.intercept.method.MockMethodInvocation;
+import org.springframework.security.access.method.MethodAuthorizationContext;
+import org.springframework.security.access.prepost.PreFilter;
+import org.springframework.security.authentication.TestAuthentication;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
+
+/**
+ * Tests for {@link PreFilterAuthorizationMethodBeforeAdvice}.
+ *
+ * @author Evgeniy Cheban
+ */
+public class PreFilterAuthorizationMethodBeforeAdviceTests {
+
+	@Test
+	public void setExpressionHandlerWhenNotNullThenSetsExpressionHandler() {
+		MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
+		PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
+		advice.setExpressionHandler(expressionHandler);
+		assertThat(advice).extracting("expressionHandler").isEqualTo(expressionHandler);
+	}
+
+	@Test
+	public void setExpressionHandlerWhenNullThenException() {
+		PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
+		assertThatIllegalArgumentException().isThrownBy(() -> advice.setExpressionHandler(null))
+				.withMessage("expressionHandler cannot be null");
+	}
+
+	@Test
+	public void methodMatcherWhenMethodHasNotPreFilterAnnotationThenNotMatches() throws Exception {
+		PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
+		MethodMatcher methodMatcher = advice.getMethodMatcher();
+		assertThat(methodMatcher.matches(TestClass.class.getMethod("doSomething"), TestClass.class)).isFalse();
+	}
+
+	@Test
+	public void methodMatcherWhenMethodHasPreFilterAnnotationThenMatches() throws Exception {
+		PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
+		MethodMatcher methodMatcher = advice.getMethodMatcher();
+		assertThat(methodMatcher.matches(TestClass.class.getMethod("doSomethingListFilterTargetMatch", List.class),
+				TestClass.class)).isTrue();
+	}
+
+	@Test
+	public void findFilterTargetWhenNameProvidedAndNotMatchThenException() throws Exception {
+		MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+				"doSomethingListFilterTargetNotMatch", new Class[] { List.class }, new Object[] { new ArrayList<>() });
+		MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
+				TestClass.class);
+		PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
+		assertThatIllegalArgumentException()
+				.isThrownBy(() -> advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext))
+				.withMessage(
+						"Filter target was null, or no argument with name 'filterTargetNotMatch' found in method.");
+	}
+
+	@Test
+	public void findFilterTargetWhenNameProvidedAndMatchAndNullThenException() throws Exception {
+		MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+				"doSomethingListFilterTargetMatch", new Class[] { List.class }, new Object[] { null });
+		MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
+				TestClass.class);
+		PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
+		assertThatIllegalArgumentException()
+				.isThrownBy(() -> advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext))
+				.withMessage("Filter target was null, or no argument with name 'list' found in method.");
+	}
+
+	@Test
+	public void findFilterTargetWhenNameProvidedAndMatchAndNotNullThenFiltersList() throws Exception {
+		List<String> list = new ArrayList<>();
+		list.add("john");
+		list.add("bob");
+		MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+				"doSomethingListFilterTargetMatch", new Class[] { List.class }, new Object[] { list });
+		MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
+				TestClass.class);
+		PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
+		advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext);
+		assertThat(list).hasSize(1);
+		assertThat(list.get(0)).isEqualTo("john");
+	}
+
+	@Test
+	public void findFilterTargetWhenNameNotProvidedAndSingleArgListNullThenException() throws Exception {
+		MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+				"doSomethingListFilterTargetNotProvided", new Class[] { List.class }, new Object[] { null });
+		MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
+				TestClass.class);
+		PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
+		assertThatIllegalArgumentException()
+				.isThrownBy(() -> advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext))
+				.withMessage("Filter target was null. Make sure you passing the correct value in the method argument.");
+	}
+
+	@Test
+	public void findFilterTargetWhenNameNotProvidedAndSingleArgListThenFiltersList() throws Exception {
+		List<String> list = new ArrayList<>();
+		list.add("john");
+		list.add("bob");
+		MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+				"doSomethingListFilterTargetNotProvided", new Class[] { List.class }, new Object[] { list });
+		MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
+				TestClass.class);
+		PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
+		advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext);
+		assertThat(list).hasSize(1);
+		assertThat(list.get(0)).isEqualTo("john");
+	}
+
+	@Test
+	public void findFilterTargetWhenNameNotProvidedAndSingleArgArrayThenException() throws Exception {
+		MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+				"doSomethingArrayFilterTargetNotProvided", new Class[] { String[].class },
+				new Object[] { new String[] {} });
+		MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
+				TestClass.class);
+		PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
+		assertThatIllegalStateException()
+				.isThrownBy(() -> advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext))
+				.withMessage(
+						"Pre-filtering on array types is not supported. Using a Collection will solve this problem.");
+	}
+
+	@Test
+	public void findFilterTargetWhenNameNotProvidedAndNotSingleArgThenException() throws Exception {
+		MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+				"doSomethingTwoArgsFilterTargetNotProvided", new Class[] { String.class, List.class },
+				new Object[] { "", new ArrayList<>() });
+		MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
+				TestClass.class);
+		PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
+		assertThatIllegalStateException()
+				.isThrownBy(() -> advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext))
+				.withMessage("Unable to determine the method argument for filtering. Specify the filter target.");
+	}
+
+	public static class TestClass {
+
+		public void doSomething() {
+
+		}
+
+		@PreFilter(value = "filterObject == 'john'", filterTarget = "filterTargetNotMatch")
+		public List<String> doSomethingListFilterTargetNotMatch(List<String> list) {
+			return list;
+		}
+
+		@PreFilter(value = "filterObject == 'john'", filterTarget = "list")
+		public List<String> doSomethingListFilterTargetMatch(List<String> list) {
+			return list;
+		}
+
+		@PreFilter("filterObject == 'john'")
+		public List<String> doSomethingListFilterTargetNotProvided(List<String> list) {
+			return list;
+		}
+
+		@PreFilter("filterObject == 'john'")
+		public String[] doSomethingArrayFilterTargetNotProvided(String[] array) {
+			return array;
+		}
+
+		@PreFilter("filterObject == 'john'")
+		public List<String> doSomethingTwoArgsFilterTargetNotProvided(String s, List<String> list) {
+			return list;
+		}
+
+	}
+
+}