From 67e5c05a47067b6a65d493529ffa485a26bec219 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Wed, 7 Apr 2021 15:33:47 -0600 Subject: [PATCH] Polish AuthorizationManager Method Security - Removed consolidated pointcut advisor in favor of each interceptor being an advisor. This allows Spring AOP to do more of the heavy lifting of selecting the set of interceptors that applies - Created new method context for after interceptors instead of modifying existing one - Added documentation - Added XML support - Added AuthorizationInterceptorsOrder to simplify interceptor ordering - Adjusted annotation lookup to comply with JSR-250 spec - Adjusted annotation lookup to exhaustively search for duplicate annotations - Separated into three @Configuration classes, one for each set of authorization annotations Issue gh-9289 --- .../security/config/Elements.java | 2 + .../config/SecurityNamespaceHandler.java | 2 + .../configuration/EnableMethodSecurity.java | 24 +- .../Jsr250MethodSecurityConfiguration.java | 54 ++ .../MethodSecurityConfiguration.java | 252 -------- .../configuration/MethodSecuritySelector.java | 47 +- .../PrePostMethodSecurityConfiguration.java | 103 +++ .../SecuredMethodSecurityConfiguration.java | 45 ++ .../MethodSecurityBeanDefinitionParser.java | 228 +++++++ .../security/config/spring-security-5.6.rnc | 16 + .../security/config/spring-security-5.6.xsd | 57 ++ .../configuration/MethodSecurityService.java | 15 + .../MethodSecurityServiceImpl.java | 11 + ...PostMethodSecurityConfigurationTests.java} | 185 ++++-- .../configuration/RequireAdminRole.java | 28 + .../method/configuration/RequireUserRole.java | 28 + .../config/doc/XsdDocumentedTests.java | 1 - ...thodSecurityBeanDefinitionParserTests.java | 385 ++++++++++++ ...nDefinitionParserTests-BusinessService.xml | 28 + ...-CustomAuthorizationManagerAfterAdvice.xml | 40 ++ ...CustomAuthorizationManagerBeforeAdvice.xml | 43 ++ ...erTests-CustomGrantedAuthorityDefaults.xml | 31 + ...nParserTests-CustomPermissionEvaluator.xml | 35 ++ ...curityBeanDefinitionParserTests-Jsr250.xml | 28 + ...itionParserTests-MethodSecurityService.xml | 28 + ...rserTests-MethodSecurityServiceEnabled.xml | 28 + ...urityBeanDefinitionParserTests-Secured.xml | 28 + .../Jsr250AuthorizationManager.java | 123 ---- .../AuthorizationMethodInterceptor.java | 82 --- ...AuthorizationManagerMethodAfterAdvice.java | 73 --- ...uthorizationManagerMethodBeforeAdvice.java | 72 --- .../AuthorizationMethodAfterAdvice.java | 61 -- .../AuthorizationMethodBeforeAdvice.java | 52 -- ...egatingAuthorizationMethodAfterAdvice.java | 102 --- ...gatingAuthorizationMethodBeforeAdvice.java | 95 --- .../method/MethodAuthorizationContext.java | 84 --- .../AbstractAuthorizationManagerRegistry.java | 25 +- .../AbstractExpressionAttributeRegistry.java | 18 +- .../method/AuthorizationAnnotationUtils.java | 113 ++++ .../AuthorizationInterceptorsOrder.java | 71 +++ ...rizationManagerAfterMethodInterceptor.java | 137 ++++ ...izationManagerBeforeMethodInterceptor.java | 183 ++++++ .../method/AuthorizationMethodPointcuts.java | 54 ++ .../method/Jsr250AuthorizationManager.java | 151 +++++ .../method/MethodInvocationResult.java | 63 ++ .../PostAuthorizeAuthorizationManager.java | 41 +- ...FilterAuthorizationMethodInterceptor.java} | 117 ++-- .../PreAuthorizeAuthorizationManager.java | 33 +- ...FilterAuthorizationMethodInterceptor.java} | 105 +++- .../method}/SecuredAuthorizationManager.java | 34 +- .../access/annotation/BusinessService.java | 6 + .../Jsr250AuthorizationManagerTests.java | 167 ----- .../access/annotation/RequireAdminRole.java | 32 + .../access/annotation/RequireUserRole.java | 32 + .../SecuredAuthorizationManagerTests.java | 104 --- .../AuthorizationMethodInterceptorTests.java | 107 ---- ...ngAuthorizationMethodAfterAdviceTests.java | 164 ----- ...gAuthorizationMethodBeforeAdviceTests.java | 168 ----- ...onManagerAfterMethodInterceptorTests.java} | 41 +- ...nManagerBeforeMethodInterceptorTests.java} | 31 +- .../AuthorizationMethodPointcutsTests.java | 167 +++++ .../Jsr250AuthorizationManagerTests.java | 278 ++++++++ ...ostAuthorizeAuthorizationManagerTests.java | 154 ++++- ...erAuthorizationMethodAfterAdviceTests.java | 96 --- ...erAuthorizationMethodInterceptorTests.java | 186 ++++++ ...PreAuthorizeAuthorizationManagerTests.java | 130 +++- ...rAuthorizationMethodBeforeAdviceTests.java | 200 ------ ...erAuthorizationMethodInterceptorTests.java | 260 ++++++++ .../SecuredAuthorizationManagerTests.java | 195 ++++++ .../asciidoc/_includes/about/whats-new.adoc | 46 +- .../_includes/servlet/appendix/namespace.adoc | 35 +- .../authorization/method-security.adoc | 592 ++++++++++++++++++ 72 files changed, 4510 insertions(+), 2342 deletions(-) create mode 100644 config/src/main/java/org/springframework/security/config/annotation/method/configuration/Jsr250MethodSecurityConfiguration.java delete mode 100644 config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityConfiguration.java create mode 100644 config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java create mode 100644 config/src/main/java/org/springframework/security/config/annotation/method/configuration/SecuredMethodSecurityConfiguration.java create mode 100644 config/src/main/java/org/springframework/security/config/method/MethodSecurityBeanDefinitionParser.java rename config/src/test/java/org/springframework/security/config/annotation/method/configuration/{MethodSecurityConfigurationTests.java => PrePostMethodSecurityConfigurationTests.java} (65%) create mode 100644 config/src/test/java/org/springframework/security/config/annotation/method/configuration/RequireAdminRole.java create mode 100644 config/src/test/java/org/springframework/security/config/annotation/method/configuration/RequireUserRole.java create mode 100644 config/src/test/java/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests.java create mode 100644 config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-BusinessService.xml create mode 100644 config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-CustomAuthorizationManagerAfterAdvice.xml create mode 100644 config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-CustomAuthorizationManagerBeforeAdvice.xml create mode 100644 config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-CustomGrantedAuthorityDefaults.xml create mode 100644 config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-CustomPermissionEvaluator.xml create mode 100644 config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-Jsr250.xml create mode 100644 config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-MethodSecurityService.xml create mode 100644 config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-MethodSecurityServiceEnabled.xml create mode 100644 config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-Secured.xml delete mode 100644 core/src/main/java/org/springframework/security/access/annotation/Jsr250AuthorizationManager.java delete mode 100644 core/src/main/java/org/springframework/security/access/intercept/aopalliance/AuthorizationMethodInterceptor.java delete mode 100644 core/src/main/java/org/springframework/security/access/method/AuthorizationManagerMethodAfterAdvice.java delete mode 100644 core/src/main/java/org/springframework/security/access/method/AuthorizationManagerMethodBeforeAdvice.java delete mode 100644 core/src/main/java/org/springframework/security/access/method/AuthorizationMethodAfterAdvice.java delete mode 100644 core/src/main/java/org/springframework/security/access/method/AuthorizationMethodBeforeAdvice.java delete mode 100644 core/src/main/java/org/springframework/security/access/method/DelegatingAuthorizationMethodAfterAdvice.java delete mode 100644 core/src/main/java/org/springframework/security/access/method/DelegatingAuthorizationMethodBeforeAdvice.java delete mode 100644 core/src/main/java/org/springframework/security/access/method/MethodAuthorizationContext.java rename core/src/main/java/org/springframework/security/{access/annotation => authorization/method}/AbstractAuthorizationManagerRegistry.java (62%) create mode 100644 core/src/main/java/org/springframework/security/authorization/method/AuthorizationAnnotationUtils.java create mode 100644 core/src/main/java/org/springframework/security/authorization/method/AuthorizationInterceptorsOrder.java create mode 100644 core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptor.java create mode 100644 core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.java create mode 100644 core/src/main/java/org/springframework/security/authorization/method/AuthorizationMethodPointcuts.java create mode 100644 core/src/main/java/org/springframework/security/authorization/method/Jsr250AuthorizationManager.java create mode 100644 core/src/main/java/org/springframework/security/authorization/method/MethodInvocationResult.java rename core/src/main/java/org/springframework/security/authorization/method/{PostFilterAuthorizationMethodAfterAdvice.java => PostFilterAuthorizationMethodInterceptor.java} (51%) rename core/src/main/java/org/springframework/security/authorization/method/{PreFilterAuthorizationMethodBeforeAdvice.java => PreFilterAuthorizationMethodInterceptor.java} (64%) rename core/src/main/java/org/springframework/security/{access/annotation => authorization/method}/SecuredAuthorizationManager.java (65%) delete mode 100644 core/src/test/java/org/springframework/security/access/annotation/Jsr250AuthorizationManagerTests.java create mode 100644 core/src/test/java/org/springframework/security/access/annotation/RequireAdminRole.java create mode 100644 core/src/test/java/org/springframework/security/access/annotation/RequireUserRole.java delete mode 100644 core/src/test/java/org/springframework/security/access/annotation/SecuredAuthorizationManagerTests.java delete mode 100644 core/src/test/java/org/springframework/security/access/intercept/aopalliance/AuthorizationMethodInterceptorTests.java delete mode 100644 core/src/test/java/org/springframework/security/access/method/DelegatingAuthorizationMethodAfterAdviceTests.java delete mode 100644 core/src/test/java/org/springframework/security/access/method/DelegatingAuthorizationMethodBeforeAdviceTests.java rename core/src/test/java/org/springframework/security/{access/method/AuthorizationManagerMethodAfterAdviceTests.java => authorization/method/AuthorizationManagerAfterMethodInterceptorTests.java} (50%) rename core/src/test/java/org/springframework/security/{access/method/AuthorizationManagerMethodBeforeAdviceTests.java => authorization/method/AuthorizationManagerBeforeMethodInterceptorTests.java} (57%) create mode 100644 core/src/test/java/org/springframework/security/authorization/method/AuthorizationMethodPointcutsTests.java create mode 100644 core/src/test/java/org/springframework/security/authorization/method/Jsr250AuthorizationManagerTests.java delete mode 100644 core/src/test/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodAfterAdviceTests.java create mode 100644 core/src/test/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptorTests.java delete mode 100644 core/src/test/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodBeforeAdviceTests.java create mode 100644 core/src/test/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptorTests.java create mode 100644 core/src/test/java/org/springframework/security/authorization/method/SecuredAuthorizationManagerTests.java diff --git a/config/src/main/java/org/springframework/security/config/Elements.java b/config/src/main/java/org/springframework/security/config/Elements.java index 20ad615769..55e0dbaa30 100644 --- a/config/src/main/java/org/springframework/security/config/Elements.java +++ b/config/src/main/java/org/springframework/security/config/Elements.java @@ -87,6 +87,8 @@ public abstract class Elements { public static final String GLOBAL_METHOD_SECURITY = "global-method-security"; + public static final String METHOD_SECURITY = "method-security"; + public static final String PASSWORD_ENCODER = "password-encoder"; public static final String PORT_MAPPINGS = "port-mappings"; diff --git a/config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java b/config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java index 9a11086870..0f23a77f70 100644 --- a/config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java +++ b/config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java @@ -44,6 +44,7 @@ import org.springframework.security.config.ldap.LdapServerBeanDefinitionParser; import org.springframework.security.config.ldap.LdapUserServiceBeanDefinitionParser; import org.springframework.security.config.method.GlobalMethodSecurityBeanDefinitionParser; import org.springframework.security.config.method.InterceptMethodsBeanDefinitionDecorator; +import org.springframework.security.config.method.MethodSecurityBeanDefinitionParser; import org.springframework.security.config.method.MethodSecurityMetadataSourceBeanDefinitionParser; import org.springframework.security.config.oauth2.client.ClientRegistrationsBeanDefinitionParser; import org.springframework.security.config.websocket.WebSocketMessageBrokerSecurityBeanDefinitionParser; @@ -169,6 +170,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler { this.parsers.put(Elements.JDBC_USER_SERVICE, new JdbcUserServiceBeanDefinitionParser()); this.parsers.put(Elements.AUTHENTICATION_PROVIDER, new AuthenticationProviderBeanDefinitionParser()); this.parsers.put(Elements.GLOBAL_METHOD_SECURITY, new GlobalMethodSecurityBeanDefinitionParser()); + this.parsers.put(Elements.METHOD_SECURITY, new MethodSecurityBeanDefinitionParser()); this.parsers.put(Elements.AUTHENTICATION_MANAGER, new AuthenticationManagerBeanDefinitionParser()); this.parsers.put(Elements.METHOD_SECURITY_METADATA_SOURCE, new MethodSecurityMetadataSourceBeanDefinitionParser()); diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/EnableMethodSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/EnableMethodSecurity.java index c64a4355be..1ddae27701 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/EnableMethodSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/EnableMethodSecurity.java @@ -25,13 +25,17 @@ 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; +import org.springframework.security.access.prepost.PostAuthorize; +import org.springframework.security.access.prepost.PostFilter; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.access.prepost.PreFilter; /** * Enables Spring Security Method Security. * @author Evgeniy Cheban - * @since 5.5 + * @author Josh Cummings + * @since 5.6 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @@ -40,6 +44,14 @@ import org.springframework.security.access.annotation.Secured; @Configuration public @interface EnableMethodSecurity { + /** + * Determines if Spring Security's {@link PreAuthorize}, {@link PostAuthorize}, + * {@link PreFilter}, and {@link PostFilter} annotations should be enabled. Default is + * true. + * @return true if pre/post annotations should be enabled false otherwise + */ + boolean prePostEnabled() default true; + /** * Determines if Spring Security's {@link Secured} annotation should be enabled. * Default is false. @@ -76,12 +88,4 @@ public @interface EnableMethodSecurity { */ AdviceMode mode() default AdviceMode.PROXY; - /** - * Indicate the ordering of the execution of the security advisor when multiple - * advices are applied at a specific joinpoint. The default is - * {@link Ordered#LOWEST_PRECEDENCE}. - * @return the order the security advisor should be applied - */ - int order() default Ordered.LOWEST_PRECEDENCE; - } diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/Jsr250MethodSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/Jsr250MethodSecurityConfiguration.java new file mode 100644 index 0000000000..9b3207116c --- /dev/null +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/Jsr250MethodSecurityConfiguration.java @@ -0,0 +1,54 @@ +/* + * 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 org.springframework.aop.Advisor; +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.Role; +import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor; +import org.springframework.security.authorization.method.Jsr250AuthorizationManager; +import org.springframework.security.config.core.GrantedAuthorityDefaults; + +/** + * {@link Configuration} for enabling JSR-250 Spring Security Method Security. + * + * @author Evgeniy Cheban + * @author Josh Cummings + * @see EnableMethodSecurity + * @since 5.6 + */ +@Configuration(proxyBeanMethods = false) +@Role(BeanDefinition.ROLE_INFRASTRUCTURE) +final class Jsr250MethodSecurityConfiguration { + + private final Jsr250AuthorizationManager jsr250AuthorizationManager = new Jsr250AuthorizationManager(); + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + Advisor jsr250AuthorizationMethodInterceptor() { + return AuthorizationManagerBeforeMethodInterceptor.jsr250(this.jsr250AuthorizationManager); + } + + @Autowired(required = false) + void setGrantedAuthorityDefaults(GrantedAuthorityDefaults grantedAuthorityDefaults) { + this.jsr250AuthorizationManager.setRolePrefix(grantedAuthorityDefaults.getRolePrefix()); + } + +} diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityConfiguration.java deleted file mode 100644 index 529a78f45c..0000000000 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityConfiguration.java +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright 2002-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.config.annotation.method.configuration; - -import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.annotation.security.DenyAll; -import javax.annotation.security.PermitAll; -import javax.annotation.security.RolesAllowed; - -import org.springframework.aop.MethodMatcher; -import org.springframework.aop.Pointcut; -import org.springframework.aop.support.AopUtils; -import org.springframework.aop.support.DefaultPointcutAdvisor; -import org.springframework.aop.support.Pointcuts; -import org.springframework.aop.support.StaticMethodMatcher; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.ImportAware; -import org.springframework.context.annotation.Role; -import org.springframework.core.annotation.AnnotatedElementUtils; -import org.springframework.core.annotation.AnnotationAttributes; -import org.springframework.core.type.AnnotationMetadata; -import org.springframework.security.access.annotation.Jsr250AuthorizationManager; -import org.springframework.security.access.annotation.Secured; -import org.springframework.security.access.annotation.SecuredAuthorizationManager; -import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; -import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; -import org.springframework.security.access.intercept.aopalliance.AuthorizationMethodInterceptor; -import org.springframework.security.access.method.AuthorizationManagerMethodAfterAdvice; -import org.springframework.security.access.method.AuthorizationManagerMethodBeforeAdvice; -import org.springframework.security.access.method.AuthorizationMethodAfterAdvice; -import org.springframework.security.access.method.AuthorizationMethodBeforeAdvice; -import org.springframework.security.access.method.DelegatingAuthorizationMethodAfterAdvice; -import org.springframework.security.access.method.DelegatingAuthorizationMethodBeforeAdvice; -import org.springframework.security.access.method.MethodAuthorizationContext; -import org.springframework.security.access.prepost.PostAuthorize; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager; -import org.springframework.security.authorization.method.PostFilterAuthorizationMethodAfterAdvice; -import org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager; -import org.springframework.security.authorization.method.PreFilterAuthorizationMethodBeforeAdvice; -import org.springframework.security.config.core.GrantedAuthorityDefaults; - -/** - * Base {@link Configuration} for enabling Spring Security Method Security. - * - * @author Evgeniy Cheban - * @see EnableMethodSecurity - * @since 5.5 - */ -@Configuration(proxyBeanMethods = false) -@Role(BeanDefinition.ROLE_INFRASTRUCTURE) -final class MethodSecurityConfiguration implements ImportAware { - - private MethodSecurityExpressionHandler methodSecurityExpressionHandler; - - private GrantedAuthorityDefaults grantedAuthorityDefaults; - - private AuthorizationMethodBeforeAdvice authorizationMethodBeforeAdvice; - - private AuthorizationMethodAfterAdvice authorizationMethodAfterAdvice; - - private AnnotationAttributes enableMethodSecurity; - - @Bean - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - DefaultPointcutAdvisor methodSecurityAdvisor(AuthorizationMethodInterceptor interceptor) { - Pointcut pointcut = Pointcuts.union(getAuthorizationMethodBeforeAdvice(), getAuthorizationMethodAfterAdvice()); - DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, interceptor); - advisor.setOrder(order()); - return advisor; - } - - @Bean - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - AuthorizationMethodInterceptor authorizationMethodInterceptor() { - return new AuthorizationMethodInterceptor(getAuthorizationMethodBeforeAdvice(), - getAuthorizationMethodAfterAdvice()); - } - - private MethodSecurityExpressionHandler getMethodSecurityExpressionHandler() { - if (this.methodSecurityExpressionHandler == null) { - this.methodSecurityExpressionHandler = new DefaultMethodSecurityExpressionHandler(); - } - return this.methodSecurityExpressionHandler; - } - - @Autowired(required = false) - void setMethodSecurityExpressionHandler(MethodSecurityExpressionHandler methodSecurityExpressionHandler) { - this.methodSecurityExpressionHandler = methodSecurityExpressionHandler; - } - - @Autowired(required = false) - void setGrantedAuthorityDefaults(GrantedAuthorityDefaults grantedAuthorityDefaults) { - this.grantedAuthorityDefaults = grantedAuthorityDefaults; - } - - private AuthorizationMethodBeforeAdvice getAuthorizationMethodBeforeAdvice() { - if (this.authorizationMethodBeforeAdvice == null) { - this.authorizationMethodBeforeAdvice = createDefaultAuthorizationMethodBeforeAdvice(); - } - return this.authorizationMethodBeforeAdvice; - } - - private AuthorizationMethodBeforeAdvice createDefaultAuthorizationMethodBeforeAdvice() { - List> beforeAdvices = new ArrayList<>(); - beforeAdvices.add(getPreFilterAuthorizationMethodBeforeAdvice()); - beforeAdvices.add(getPreAuthorizeAuthorizationMethodBeforeAdvice()); - if (securedEnabled()) { - beforeAdvices.add(getSecuredAuthorizationMethodBeforeAdvice()); - } - if (jsr250Enabled()) { - beforeAdvices.add(getJsr250AuthorizationMethodBeforeAdvice()); - } - return new DelegatingAuthorizationMethodBeforeAdvice(beforeAdvices); - } - - private PreFilterAuthorizationMethodBeforeAdvice getPreFilterAuthorizationMethodBeforeAdvice() { - PreFilterAuthorizationMethodBeforeAdvice preFilterBeforeAdvice = new PreFilterAuthorizationMethodBeforeAdvice(); - preFilterBeforeAdvice.setExpressionHandler(getMethodSecurityExpressionHandler()); - return preFilterBeforeAdvice; - } - - private AuthorizationMethodBeforeAdvice getPreAuthorizeAuthorizationMethodBeforeAdvice() { - MethodMatcher methodMatcher = new SecurityAnnotationsStaticMethodMatcher(PreAuthorize.class); - PreAuthorizeAuthorizationManager authorizationManager = new PreAuthorizeAuthorizationManager(); - authorizationManager.setExpressionHandler(getMethodSecurityExpressionHandler()); - return new AuthorizationManagerMethodBeforeAdvice<>(methodMatcher, authorizationManager); - } - - private AuthorizationManagerMethodBeforeAdvice getSecuredAuthorizationMethodBeforeAdvice() { - MethodMatcher methodMatcher = new SecurityAnnotationsStaticMethodMatcher(Secured.class); - SecuredAuthorizationManager authorizationManager = new SecuredAuthorizationManager(); - return new AuthorizationManagerMethodBeforeAdvice<>(methodMatcher, authorizationManager); - } - - private AuthorizationManagerMethodBeforeAdvice getJsr250AuthorizationMethodBeforeAdvice() { - MethodMatcher methodMatcher = new SecurityAnnotationsStaticMethodMatcher(DenyAll.class, PermitAll.class, - RolesAllowed.class); - Jsr250AuthorizationManager authorizationManager = new Jsr250AuthorizationManager(); - if (this.grantedAuthorityDefaults != null) { - authorizationManager.setRolePrefix(this.grantedAuthorityDefaults.getRolePrefix()); - } - return new AuthorizationManagerMethodBeforeAdvice<>(methodMatcher, authorizationManager); - } - - @Autowired(required = false) - void setAuthorizationMethodBeforeAdvice( - AuthorizationMethodBeforeAdvice authorizationMethodBeforeAdvice) { - this.authorizationMethodBeforeAdvice = authorizationMethodBeforeAdvice; - } - - private AuthorizationMethodAfterAdvice getAuthorizationMethodAfterAdvice() { - if (this.authorizationMethodAfterAdvice == null) { - this.authorizationMethodAfterAdvice = createDefaultAuthorizationMethodAfterAdvice(); - } - return this.authorizationMethodAfterAdvice; - } - - private AuthorizationMethodAfterAdvice createDefaultAuthorizationMethodAfterAdvice() { - List> afterAdvices = new ArrayList<>(); - afterAdvices.add(getPostFilterAuthorizationMethodAfterAdvice()); - afterAdvices.add(getPostAuthorizeAuthorizationMethodAfterAdvice()); - return new DelegatingAuthorizationMethodAfterAdvice(afterAdvices); - } - - private PostFilterAuthorizationMethodAfterAdvice getPostFilterAuthorizationMethodAfterAdvice() { - PostFilterAuthorizationMethodAfterAdvice postFilterAfterAdvice = new PostFilterAuthorizationMethodAfterAdvice(); - postFilterAfterAdvice.setExpressionHandler(getMethodSecurityExpressionHandler()); - return postFilterAfterAdvice; - } - - private AuthorizationManagerMethodAfterAdvice getPostAuthorizeAuthorizationMethodAfterAdvice() { - MethodMatcher methodMatcher = new SecurityAnnotationsStaticMethodMatcher(PostAuthorize.class); - PostAuthorizeAuthorizationManager authorizationManager = new PostAuthorizeAuthorizationManager(); - authorizationManager.setExpressionHandler(getMethodSecurityExpressionHandler()); - return new AuthorizationManagerMethodAfterAdvice<>(methodMatcher, authorizationManager); - } - - @Autowired(required = false) - void setAuthorizationMethodAfterAdvice( - AuthorizationMethodAfterAdvice authorizationMethodAfterAdvice) { - this.authorizationMethodAfterAdvice = authorizationMethodAfterAdvice; - } - - @Override - public void setImportMetadata(AnnotationMetadata importMetadata) { - Map attributes = importMetadata.getAnnotationAttributes(EnableMethodSecurity.class.getName()); - this.enableMethodSecurity = AnnotationAttributes.fromMap(attributes); - } - - private boolean securedEnabled() { - return this.enableMethodSecurity.getBoolean("securedEnabled"); - } - - private boolean jsr250Enabled() { - return this.enableMethodSecurity.getBoolean("jsr250Enabled"); - } - - private int order() { - return this.enableMethodSecurity.getNumber("order"); - } - - private static final class SecurityAnnotationsStaticMethodMatcher extends StaticMethodMatcher { - - private final Set> annotationClasses; - - @SafeVarargs - private SecurityAnnotationsStaticMethodMatcher(Class... annotationClasses) { - this.annotationClasses = new HashSet<>(Arrays.asList(annotationClasses)); - } - - @Override - public boolean matches(Method method, Class targetClass) { - Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass); - return hasAnnotations(specificMethod) || hasAnnotations(specificMethod.getDeclaringClass()); - } - - private boolean hasAnnotations(AnnotatedElement annotatedElement) { - Set annotations = AnnotatedElementUtils.findAllMergedAnnotations(annotatedElement, - this.annotationClasses); - return !annotations.isEmpty(); - } - - } - -} diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecuritySelector.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecuritySelector.java index ed0df62454..0443a03e1c 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecuritySelector.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecuritySelector.java @@ -17,34 +17,59 @@ package org.springframework.security.config.annotation.method.configuration; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.springframework.context.annotation.AdviceMode; import org.springframework.context.annotation.AdviceModeImportSelector; import org.springframework.context.annotation.AutoProxyRegistrar; +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.lang.NonNull; /** * Dynamically determines which imports to include using the {@link EnableMethodSecurity} * annotation. * * @author Evgeniy Cheban - * @since 5.5 + * @author Josh Cummings + * @since 5.6 */ -final class MethodSecuritySelector extends AdviceModeImportSelector { +final class MethodSecuritySelector implements ImportSelector { + + private final ImportSelector autoProxy = new AutoProxyRegistrarSelector(); @Override - protected String[] selectImports(AdviceMode adviceMode) { - if (adviceMode == AdviceMode.PROXY) { - return getProxyImports(); + public String[] selectImports(@NonNull AnnotationMetadata importMetadata) { + if (!importMetadata.hasAnnotation(EnableMethodSecurity.class.getName())) { + return new String[0]; } - throw new IllegalStateException("AdviceMode '" + adviceMode + "' is not supported"); + EnableMethodSecurity annotation = importMetadata.getAnnotations().get(EnableMethodSecurity.class).synthesize(); + List imports = new ArrayList<>(Arrays.asList(this.autoProxy.selectImports(importMetadata))); + if (annotation.prePostEnabled()) { + imports.add(PrePostMethodSecurityConfiguration.class.getName()); + } + if (annotation.securedEnabled()) { + imports.add(SecuredMethodSecurityConfiguration.class.getName()); + } + if (annotation.jsr250Enabled()) { + imports.add(Jsr250MethodSecurityConfiguration.class.getName()); + } + return imports.toArray(new String[0]); } - private String[] getProxyImports() { - List result = new ArrayList<>(); - result.add(AutoProxyRegistrar.class.getName()); - result.add(MethodSecurityConfiguration.class.getName()); - return result.toArray(new String[0]); + private static final class AutoProxyRegistrarSelector extends AdviceModeImportSelector { + + private static final String[] IMPORTS = new String[] { AutoProxyRegistrar.class.getName() }; + + @Override + protected String[] selectImports(@NonNull AdviceMode adviceMode) { + if (adviceMode == AdviceMode.PROXY) { + return IMPORTS; + } + throw new IllegalStateException("AdviceMode '" + adviceMode + "' is not supported"); + } + } } diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java new file mode 100644 index 0000000000..3a3bd01d38 --- /dev/null +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java @@ -0,0 +1,103 @@ +/* + * 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 org.springframework.aop.Advisor; +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.Role; +import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; +import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; +import org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor; +import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor; +import org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager; +import org.springframework.security.authorization.method.PostFilterAuthorizationMethodInterceptor; +import org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager; +import org.springframework.security.authorization.method.PreFilterAuthorizationMethodInterceptor; +import org.springframework.security.config.core.GrantedAuthorityDefaults; + +/** + * Base {@link Configuration} for enabling Spring Security Method Security. + * + * @author Evgeniy Cheban + * @author Josh Cummings + * @see EnableMethodSecurity + * @since 5.6 + */ +@Configuration(proxyBeanMethods = false) +@Role(BeanDefinition.ROLE_INFRASTRUCTURE) +final class PrePostMethodSecurityConfiguration { + + private final PreFilterAuthorizationMethodInterceptor preFilterAuthorizationMethodInterceptor = new PreFilterAuthorizationMethodInterceptor(); + + private final PreAuthorizeAuthorizationManager preAuthorizeAuthorizationManager = new PreAuthorizeAuthorizationManager(); + + private final PostAuthorizeAuthorizationManager postAuthorizeAuthorizationManager = new PostAuthorizeAuthorizationManager(); + + private final PostFilterAuthorizationMethodInterceptor postFilterAuthorizationMethodInterceptor = new PostFilterAuthorizationMethodInterceptor(); + + private boolean customMethodSecurityExpressionHandler = false; + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + Advisor preFilterAuthorizationMethodInterceptor() { + return this.preFilterAuthorizationMethodInterceptor; + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + Advisor preAuthorizeAuthorizationMethodInterceptor() { + return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(this.preAuthorizeAuthorizationManager); + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + Advisor postAuthorizeAuthorizationMethodInterceptor() { + return AuthorizationManagerAfterMethodInterceptor.postAuthorize(this.postAuthorizeAuthorizationManager); + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + Advisor postFilterAuthorizationMethodInterceptor() { + return this.postFilterAuthorizationMethodInterceptor; + } + + @Autowired(required = false) + void setMethodSecurityExpressionHandler(MethodSecurityExpressionHandler methodSecurityExpressionHandler) { + this.customMethodSecurityExpressionHandler = true; + this.preFilterAuthorizationMethodInterceptor.setExpressionHandler(methodSecurityExpressionHandler); + this.preAuthorizeAuthorizationManager.setExpressionHandler(methodSecurityExpressionHandler); + this.postAuthorizeAuthorizationManager.setExpressionHandler(methodSecurityExpressionHandler); + this.postFilterAuthorizationMethodInterceptor.setExpressionHandler(methodSecurityExpressionHandler); + } + + @Autowired(required = false) + void setGrantedAuthorityDefaults(GrantedAuthorityDefaults grantedAuthorityDefaults) { + if (this.customMethodSecurityExpressionHandler) { + return; + } + DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); + expressionHandler.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix()); + this.preFilterAuthorizationMethodInterceptor.setExpressionHandler(expressionHandler); + this.preAuthorizeAuthorizationManager.setExpressionHandler(expressionHandler); + this.postAuthorizeAuthorizationManager.setExpressionHandler(expressionHandler); + this.postFilterAuthorizationMethodInterceptor.setExpressionHandler(expressionHandler); + } + +} diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/SecuredMethodSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/SecuredMethodSecurityConfiguration.java new file mode 100644 index 0000000000..5b7fc7358b --- /dev/null +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/SecuredMethodSecurityConfiguration.java @@ -0,0 +1,45 @@ +/* + * 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 org.springframework.aop.Advisor; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Role; +import org.springframework.security.access.annotation.Secured; +import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor; + +/** + * {@link Configuration} for enabling {@link Secured} Spring Security Method Security. + * + * @author Evgeniy Cheban + * @author Josh Cummings + * @see EnableMethodSecurity + * @since 5.6 + */ +@Configuration(proxyBeanMethods = false) +@Role(BeanDefinition.ROLE_INFRASTRUCTURE) +final class SecuredMethodSecurityConfiguration { + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + Advisor securedAuthorizationMethodInterceptor() { + return AuthorizationManagerBeforeMethodInterceptor.secured(); + } + +} diff --git a/config/src/main/java/org/springframework/security/config/method/MethodSecurityBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/method/MethodSecurityBeanDefinitionParser.java new file mode 100644 index 0000000000..cbb1763872 --- /dev/null +++ b/config/src/main/java/org/springframework/security/config/method/MethodSecurityBeanDefinitionParser.java @@ -0,0 +1,228 @@ +/* + * Copyright 2002-2018 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.method; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.w3c.dom.Element; + +import org.springframework.aop.config.AopNamespaceUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.parsing.CompositeComponentDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; +import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; +import org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor; +import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor; +import org.springframework.security.authorization.method.Jsr250AuthorizationManager; +import org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager; +import org.springframework.security.authorization.method.PostFilterAuthorizationMethodInterceptor; +import org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager; +import org.springframework.security.authorization.method.PreFilterAuthorizationMethodInterceptor; +import org.springframework.security.config.Elements; +import org.springframework.security.config.core.GrantedAuthorityDefaults; +import org.springframework.util.xml.DomUtils; + +/** + * Processes the top-level "method-security" element. + * + * @author Josh Cummings + * @since 5.6 + */ +public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser { + + private final Log logger = LogFactory.getLog(getClass()); + + private static final String ATT_USE_JSR250 = "jsr250-enabled"; + + private static final String ATT_USE_SECURED = "secured-enabled"; + + private static final String ATT_USE_PREPOST = "pre-post-enabled"; + + private static final String ATT_REF = "ref"; + + @Override + public BeanDefinition parse(Element element, ParserContext pc) { + CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), + pc.extractSource(element)); + pc.pushContainingComponent(compositeDef); + boolean prePostAnnotationsEnabled = !element.hasAttribute(ATT_USE_PREPOST) + || "true".equals(element.getAttribute(ATT_USE_PREPOST)); + if (prePostAnnotationsEnabled) { + BeanDefinitionBuilder preFilterInterceptor = BeanDefinitionBuilder + .rootBeanDefinition(PreFilterAuthorizationMethodInterceptor.class) + .setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + BeanDefinitionBuilder preAuthorizeInterceptor = BeanDefinitionBuilder + .rootBeanDefinition(PreAuthorizeAuthorizationMethodInterceptor.class) + .setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + BeanDefinitionBuilder postAuthorizeInterceptor = BeanDefinitionBuilder + .rootBeanDefinition(PostAuthorizeAuthorizationMethodInterceptor.class) + .setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + BeanDefinitionBuilder postFilterInterceptor = BeanDefinitionBuilder + .rootBeanDefinition(PostFilterAuthorizationMethodInterceptor.class) + .setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + Element expressionHandlerElt = DomUtils.getChildElementByTagName(element, Elements.EXPRESSION_HANDLER); + if (expressionHandlerElt != null) { + String expressionHandlerRef = expressionHandlerElt.getAttribute(ATT_REF); + preFilterInterceptor.addPropertyReference("expressionHandler", expressionHandlerRef); + preAuthorizeInterceptor.addPropertyReference("expressionHandler", expressionHandlerRef); + postAuthorizeInterceptor.addPropertyReference("expressionHandler", expressionHandlerRef); + postFilterInterceptor.addPropertyReference("expressionHandler", expressionHandlerRef); + } + else { + BeanDefinition expressionHandler = BeanDefinitionBuilder + .rootBeanDefinition(MethodSecurityExpressionHandlerBean.class).getBeanDefinition(); + preFilterInterceptor.addPropertyValue("expressionHandler", expressionHandler); + preAuthorizeInterceptor.addPropertyValue("expressionHandler", expressionHandler); + postAuthorizeInterceptor.addPropertyValue("expressionHandler", expressionHandler); + postFilterInterceptor.addPropertyValue("expressionHandler", expressionHandler); + } + pc.getRegistry().registerBeanDefinition("preFilterAuthorizationMethodInterceptor", + preFilterInterceptor.getBeanDefinition()); + pc.getRegistry().registerBeanDefinition("preAuthorizeAuthorizationMethodInterceptor", + preAuthorizeInterceptor.getBeanDefinition()); + pc.getRegistry().registerBeanDefinition("postAuthorizeAuthorizationMethodInterceptor", + postAuthorizeInterceptor.getBeanDefinition()); + pc.getRegistry().registerBeanDefinition("postFilterAuthorizationMethodInterceptor", + postFilterInterceptor.getBeanDefinition()); + } + boolean securedEnabled = "true".equals(element.getAttribute(ATT_USE_SECURED)); + if (securedEnabled) { + BeanDefinitionBuilder securedInterceptor = BeanDefinitionBuilder + .rootBeanDefinition(AuthorizationManagerBeforeMethodInterceptor.class) + .setRole(BeanDefinition.ROLE_INFRASTRUCTURE).setFactoryMethod("secured"); + pc.getRegistry().registerBeanDefinition("securedAuthorizationMethodInterceptor", + securedInterceptor.getBeanDefinition()); + } + boolean jsr250Enabled = "true".equals(element.getAttribute(ATT_USE_JSR250)); + if (jsr250Enabled) { + BeanDefinitionBuilder jsr250Interceptor = BeanDefinitionBuilder + .rootBeanDefinition(Jsr250AuthorizationMethodInterceptor.class) + .setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + pc.getRegistry().registerBeanDefinition("jsr250AuthorizationMethodInterceptor", + jsr250Interceptor.getBeanDefinition()); + } + AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(pc, element); + pc.popAndRegisterContainingComponent(); + return null; + } + + public static final class MethodSecurityExpressionHandlerBean + implements FactoryBean, ApplicationContextAware { + + private final DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); + + @Override + public MethodSecurityExpressionHandler getObject() { + return this.expressionHandler; + } + + @Override + public Class getObjectType() { + return MethodSecurityExpressionHandler.class; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + String[] grantedAuthorityDefaultsBeanNames = applicationContext + .getBeanNamesForType(GrantedAuthorityDefaults.class); + if (grantedAuthorityDefaultsBeanNames.length == 1) { + GrantedAuthorityDefaults grantedAuthorityDefaults = applicationContext + .getBean(grantedAuthorityDefaultsBeanNames[0], GrantedAuthorityDefaults.class); + this.expressionHandler.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix()); + } + } + + } + + public static final class Jsr250AuthorizationMethodInterceptor + implements FactoryBean, ApplicationContextAware { + + private final Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); + + @Override + public AuthorizationManagerBeforeMethodInterceptor getObject() { + return AuthorizationManagerBeforeMethodInterceptor.jsr250(this.manager); + } + + @Override + public Class getObjectType() { + return AuthorizationManagerBeforeMethodInterceptor.class; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + String[] grantedAuthorityDefaultsBeanNames = applicationContext + .getBeanNamesForType(GrantedAuthorityDefaults.class); + if (grantedAuthorityDefaultsBeanNames.length == 1) { + GrantedAuthorityDefaults grantedAuthorityDefaults = applicationContext + .getBean(grantedAuthorityDefaultsBeanNames[0], GrantedAuthorityDefaults.class); + this.manager.setRolePrefix(grantedAuthorityDefaults.getRolePrefix()); + } + } + + } + + public static final class PreAuthorizeAuthorizationMethodInterceptor + implements FactoryBean { + + private final PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager(); + + @Override + public AuthorizationManagerBeforeMethodInterceptor getObject() { + return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(this.manager); + } + + @Override + public Class getObjectType() { + return AuthorizationManagerBeforeMethodInterceptor.class; + } + + public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { + this.manager.setExpressionHandler(expressionHandler); + } + + } + + public static final class PostAuthorizeAuthorizationMethodInterceptor + implements FactoryBean { + + private final PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager(); + + @Override + public AuthorizationManagerAfterMethodInterceptor getObject() { + return AuthorizationManagerAfterMethodInterceptor.postAuthorize(this.manager); + } + + @Override + public Class getObjectType() { + return AuthorizationManagerAfterMethodInterceptor.class; + } + + public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { + this.manager.setExpressionHandler(expressionHandler); + } + + } + +} diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-5.6.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-5.6.rnc index 4d94b28a54..72f0eb673b 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-5.6.rnc +++ b/config/src/main/resources/org/springframework/security/config/spring-security-5.6.rnc @@ -196,6 +196,22 @@ msmds.attlist &= id? msmds.attlist &= use-expressions? +method-security = + ## Provides method security for all beans registered in the Spring application context. Specifically, beans will be scanned for matches with Spring Security annotations. Where there is a match, the beans will automatically be proxied and security authorization applied to the methods accordingly. Interceptors are invoked in the order specified in AuthorizationInterceptorsOrder. Use can create your own interceptors using Spring AOP. + element method-security {method-security.attlist, expression-handler?} +method-security.attlist &= + ## Specifies whether the use of Spring Security's pre and post invocation annotations (@PreFilter, @PreAuthorize, @PostFilter, @PostAuthorize) should be enabled for this application context. Defaults to "true". + attribute pre-post-enabled {xsd:boolean}? +method-security.attlist &= + ## Specifies whether the use of Spring Security's @Secured annotations should be enabled for this application context. Defaults to "false". + attribute secured-enabled {xsd:boolean}? +method-security.attlist &= + ## Specifies whether JSR-250 style attributes are to be used (for example "RolesAllowed"). This will require the javax.annotation.security classes on the classpath. Defaults to "false". + attribute jsr250-enabled {xsd:boolean}? +method-security.attlist &= + ## If true, class-based proxying will be used instead of interface-based proxying. + attribute proxy-target-class {xsd:boolean}? + global-method-security = ## Provides method security for all beans registered in the Spring application context. Specifically, beans will be scanned for matches with the ordered list of "protect-pointcut" sub-elements, Spring Security annotations and/or. Where there is a match, the beans will automatically be proxied and security authorization applied to the methods accordingly. If you use and enable all four sources of method security metadata (ie "protect-pointcut" declarations, expression annotations, @Secured and also JSR250 security annotations), the metadata sources will be queried in that order. In practical terms, this enables you to use XML to override method security metadata expressed in annotations. If using annotations, the order of precedence is EL-based (@PreAuthorize etc.), @Secured and finally JSR-250. element global-method-security {global-method-security.attlist, (pre-post-annotation-handling | expression-handler)?, protect-pointcut*, after-invocation-provider*} diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-5.6.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-5.6.xsd index 74bb4fe019..e8004b3efe 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-5.6.xsd +++ b/config/src/main/resources/org/springframework/security/config/spring-security-5.6.xsd @@ -595,6 +595,63 @@ + + + Provides method security for all beans registered in the Spring application context. + Specifically, beans will be scanned for matches with Spring Security annotations. Where + there is a match, the beans will automatically be proxied and security authorization + applied to the methods accordingly. Interceptors are invoked in the order specified in + AuthorizationInterceptorsOrder. Use can create your own interceptors using Spring AOP. + + + + + + + Defines the SecurityExpressionHandler instance which will be used if expression-based + access-control is enabled. A default implementation (with no ACL support) will be used if + not supplied. + + + + + + + + + + + + + + Specifies whether the use of Spring Security's pre and post invocation annotations + (@PreFilter, @PreAuthorize, @PostFilter, @PostAuthorize) should be enabled for this + application context. Defaults to "true". + + + + + + Specifies whether the use of Spring Security's @Secured annotations should be enabled for + this application context. Defaults to "false". + + + + + + Specifies whether JSR-250 style attributes are to be used (for example "RolesAllowed"). + This will require the javax.annotation.security classes on the classpath. Defaults to + "false". + + + + + + If true, class-based proxying will be used instead of interface-based proxying. + + + + Provides method security for all beans registered in the Spring application context. diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.java index 525ce2a477..b3675705c0 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.java @@ -16,12 +16,16 @@ package org.springframework.security.config.annotation.method.configuration; +import java.util.List; + import javax.annotation.security.DenyAll; import javax.annotation.security.PermitAll; import org.springframework.security.access.annotation.Secured; import org.springframework.security.access.prepost.PostAuthorize; +import org.springframework.security.access.prepost.PostFilter; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.access.prepost.PreFilter; import org.springframework.security.core.Authentication; import org.springframework.security.core.parameters.P; @@ -69,4 +73,15 @@ public interface MethodSecurityService { @PostAuthorize("#o?.contains('grant')") String postAnnotation(@P("o") String object); + @PreFilter("filterObject.length > 3") + @PreAuthorize("hasRole('ADMIN')") + @Secured("ROLE_USER") + @PostFilter("filterObject.length > 5") + @PostAuthorize("returnObject.size == 2") + List manyAnnotations(List array); + + @RequireUserRole + @RequireAdminRole + void repeatedAnnotations(); + } diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.java index 94a05216bc..cab0631af7 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.java @@ -16,6 +16,8 @@ package org.springframework.security.config.annotation.method.configuration; +import java.util.List; + import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -86,4 +88,13 @@ public class MethodSecurityServiceImpl implements MethodSecurityService { return null; } + @Override + public List manyAnnotations(List object) { + return object; + } + + @Override + public void repeatedAnnotations() { + } + } diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java similarity index 65% rename from config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityConfigurationTests.java rename to config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java index 41bf8f7c3c..aa967cbe5d 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityConfigurationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java @@ -18,31 +18,39 @@ package org.springframework.security.config.annotation.method.configuration; import java.io.Serializable; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -import java.util.function.Supplier; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.aop.MethodMatcher; +import org.springframework.aop.Advisor; +import org.springframework.aop.support.DefaultPointcutAdvisor; import org.springframework.aop.support.JdkRegexpMethodPointcut; 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.Role; +import org.springframework.core.annotation.AnnotationConfigurationException; 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.BusinessServiceImpl; import org.springframework.security.access.annotation.ExpressionProtectedBusinessServiceImpl; +import org.springframework.security.access.annotation.Jsr250BusinessServiceImpl; 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.authorization.method.AuthorizationInterceptorsOrder; +import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor; +import org.springframework.security.config.core.GrantedAuthorityDefaults; import org.springframework.security.config.test.SpringTestRule; import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; import org.springframework.security.test.context.support.WithAnonymousUser; import org.springframework.security.test.context.support.WithMockUser; @@ -52,13 +60,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** - * Tests for {@link MethodSecurityConfiguration}. + * Tests for {@link PrePostMethodSecurityConfiguration}. * * @author Evgeniy Cheban + * @author Josh Cummings */ @RunWith(SpringRunner.class) @SecurityTestExecutionListeners -public class MethodSecurityConfigurationTests { +public class PrePostMethodSecurityConfigurationTests { @Rule public final SpringTestRule spring = new SpringTestRule(); @@ -103,7 +112,7 @@ public class MethodSecurityConfigurationTests { @WithMockUser @Test public void securedWhenRoleUserThenAccessDeniedException() { - this.spring.register(MethodSecurityServiceConfig.class).autowire(); + this.spring.register(MethodSecurityServiceEnabledConfig.class).autowire(); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::secured) .withMessage("Access Denied"); } @@ -119,7 +128,7 @@ public class MethodSecurityConfigurationTests { @WithMockUser(roles = "ADMIN") @Test public void securedUserWhenRoleAdminThenAccessDeniedException() { - this.spring.register(MethodSecurityServiceConfig.class).autowire(); + this.spring.register(MethodSecurityServiceEnabledConfig.class).autowire(); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::securedUser) .withMessage("Access Denied"); } @@ -147,6 +156,13 @@ public class MethodSecurityConfigurationTests { this.methodSecurityService.preAuthorizeAdmin(); } + @WithMockUser(authorities = "PREFIX_ADMIN") + @Test + public void preAuthorizeAdminWhenRoleAdminAndCustomPrefixThenPasses() { + this.spring.register(CustomGrantedAuthorityDefaultsConfig.class, MethodSecurityServiceConfig.class).autowire(); + this.methodSecurityService.preAuthorizeAdmin(); + } + @WithMockUser @Test public void postHasPermissionWhenParameterIsNotGrantThenAccessDeniedException() { @@ -244,7 +260,7 @@ public class MethodSecurityConfigurationTests { @WithMockUser(roles = "ADMIN") @Test public void jsr250WhenRoleAdminThenAccessDeniedException() { - this.spring.register(MethodSecurityServiceConfig.class).autowire(); + this.spring.register(MethodSecurityServiceEnabledConfig.class).autowire(); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::jsr250) .withMessage("Access Denied"); } @@ -252,7 +268,7 @@ public class MethodSecurityConfigurationTests { @WithAnonymousUser @Test public void jsr250PermitAllWhenRoleAnonymousThenPasses() { - this.spring.register(MethodSecurityServiceConfig.class).autowire(); + this.spring.register(MethodSecurityServiceEnabledConfig.class).autowire(); String result = this.methodSecurityService.jsr250PermitAll(); assertThat(result).isNull(); } @@ -272,7 +288,70 @@ public class MethodSecurityConfigurationTests { this.businessService.rolesAllowedUser(); } - @EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true) + @WithMockUser(roles = { "ADMIN", "USER" }) + @Test + public void manyAnnotationsWhenMeetsConditionsThenReturnsFilteredList() throws Exception { + List names = Arrays.asList("harold", "jonathan", "pete", "bo"); + this.spring.register(MethodSecurityServiceEnabledConfig.class).autowire(); + List filtered = this.methodSecurityService.manyAnnotations(new ArrayList<>(names)); + assertThat(filtered).hasSize(2); + assertThat(filtered).containsExactly("harold", "jonathan"); + } + + // gh-4003 + // gh-4103 + @WithMockUser + @Test + public void manyAnnotationsWhenUserThenFails() { + List names = Arrays.asList("harold", "jonathan", "pete", "bo"); + this.spring.register(MethodSecurityServiceEnabledConfig.class).autowire(); + assertThatExceptionOfType(AccessDeniedException.class) + .isThrownBy(() -> this.methodSecurityService.manyAnnotations(new ArrayList<>(names))); + } + + @WithMockUser + @Test + public void manyAnnotationsWhenShortListThenFails() { + List names = Arrays.asList("harold", "jonathan", "pete"); + this.spring.register(MethodSecurityServiceEnabledConfig.class).autowire(); + assertThatExceptionOfType(AccessDeniedException.class) + .isThrownBy(() -> this.methodSecurityService.manyAnnotations(new ArrayList<>(names))); + } + + @WithMockUser(roles = "ADMIN") + @Test + public void manyAnnotationsWhenAdminThenFails() { + List names = Arrays.asList("harold", "jonathan", "pete", "bo"); + this.spring.register(MethodSecurityServiceEnabledConfig.class).autowire(); + assertThatExceptionOfType(AccessDeniedException.class) + .isThrownBy(() -> this.methodSecurityService.manyAnnotations(new ArrayList<>(names))); + } + + // gh-3183 + @Test + public void repeatedAnnotationsWhenPresentThenFails() { + this.spring.register(MethodSecurityServiceConfig.class).autowire(); + assertThatExceptionOfType(AnnotationConfigurationException.class) + .isThrownBy(() -> this.methodSecurityService.repeatedAnnotations()); + } + + // gh-3183 + @Test + public void repeatedJsr250AnnotationsWhenPresentThenFails() { + this.spring.register(Jsr250Config.class).autowire(); + assertThatExceptionOfType(AnnotationConfigurationException.class) + .isThrownBy(() -> this.businessService.repeatedAnnotations()); + } + + // gh-3183 + @Test + public void repeatedSecuredAnnotationsWhenPresentThenFails() { + this.spring.register(SecuredConfig.class).autowire(); + assertThatExceptionOfType(AnnotationConfigurationException.class) + .isThrownBy(() -> this.businessService.repeatedAnnotations()); + } + + @EnableMethodSecurity static class MethodSecurityServiceConfig { @Bean @@ -292,6 +371,36 @@ public class MethodSecurityConfigurationTests { } + @EnableMethodSecurity(prePostEnabled = false, securedEnabled = true) + static class SecuredConfig { + + @Bean + BusinessService businessService() { + return new BusinessServiceImpl<>(); + } + + } + + @EnableMethodSecurity(prePostEnabled = false, jsr250Enabled = true) + static class Jsr250Config { + + @Bean + BusinessService businessService() { + return new Jsr250BusinessServiceImpl(); + } + + } + + @EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true) + static class MethodSecurityServiceEnabledConfig { + + @Bean + MethodSecurityService methodSecurityService() { + return new MethodSecurityServiceImpl(); + } + + } + @EnableMethodSecurity static class CustomPermissionEvaluatorConfig { @@ -316,16 +425,27 @@ public class MethodSecurityConfigurationTests { } + @EnableMethodSecurity + static class CustomGrantedAuthorityDefaultsConfig { + + @Bean + GrantedAuthorityDefaults grantedAuthorityDefaults() { + return new GrantedAuthorityDefaults("PREFIX_"); + } + + } + @EnableMethodSecurity static class CustomAuthorizationManagerBeforeAdviceConfig { @Bean - AuthorizationMethodBeforeAdvice customBeforeAdvice() { - JdkRegexpMethodPointcut methodMatcher = new JdkRegexpMethodPointcut(); - methodMatcher.setPattern(".*MethodSecurityServiceImpl.*securedUser"); - AuthorizationManager authorizationManager = (a, + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + Advisor customBeforeAdvice() { + JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut(); + pointcut.setPattern(".*MethodSecurityServiceImpl.*securedUser"); + AuthorizationManager authorizationManager = (a, o) -> new AuthorizationDecision("bob".equals(a.get().getName())); - return new AuthorizationManagerMethodBeforeAdvice<>(methodMatcher, authorizationManager); + return new AuthorizationManagerBeforeMethodInterceptor(pointcut, authorizationManager); } } @@ -334,25 +454,20 @@ public class MethodSecurityConfigurationTests { static class CustomAuthorizationManagerAfterAdviceConfig { @Bean - AuthorizationMethodAfterAdvice customAfterAdvice() { - JdkRegexpMethodPointcut methodMatcher = new JdkRegexpMethodPointcut(); - methodMatcher.setPattern(".*MethodSecurityServiceImpl.*securedUser"); - return new AuthorizationMethodAfterAdvice() { - @Override - public MethodMatcher getMethodMatcher() { - return methodMatcher; - } - - @Override - public Object after(Supplier authentication, - MethodAuthorizationContext methodAuthorizationContext, Object returnedObject) { - Authentication auth = authentication.get(); - if ("bob".equals(auth.getName())) { - return "granted"; - } - throw new AccessDeniedException("Access Denied for User '" + auth.getName() + "'"); + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + Advisor customAfterAdvice() { + JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut(); + pointcut.setPattern(".*MethodSecurityServiceImpl.*securedUser"); + MethodInterceptor interceptor = (mi) -> { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if ("bob".equals(auth.getName())) { + return "granted"; } + throw new AccessDeniedException("Access Denied for User '" + auth.getName() + "'"); }; + DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, interceptor); + advisor.setOrder(AuthorizationInterceptorsOrder.POST_FILTER.getOrder() + 1); + return advisor; } } diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/RequireAdminRole.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/RequireAdminRole.java new file mode 100644 index 0000000000..5415bdb04a --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/RequireAdminRole.java @@ -0,0 +1,28 @@ +/* + * 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.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.springframework.security.access.prepost.PreAuthorize; + +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasRole('ADMIN')") +public @interface RequireAdminRole { + +} diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/RequireUserRole.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/RequireUserRole.java new file mode 100644 index 0000000000..950033dbcb --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/RequireUserRole.java @@ -0,0 +1,28 @@ +/* + * 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.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.springframework.security.access.prepost.PreAuthorize; + +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasRole('USER')") +public @interface RequireUserRole { + +} diff --git a/config/src/test/java/org/springframework/security/config/doc/XsdDocumentedTests.java b/config/src/test/java/org/springframework/security/config/doc/XsdDocumentedTests.java index 506386e98d..fc57a11a18 100644 --- a/config/src/test/java/org/springframework/security/config/doc/XsdDocumentedTests.java +++ b/config/src/test/java/org/springframework/security/config/doc/XsdDocumentedTests.java @@ -52,7 +52,6 @@ public class XsdDocumentedTests { "nsa-authentication", "nsa-websocket-security", "nsa-ldap", - "nsa-method-security", "nsa-web", // deprecated and for removal "nsa-frame-options-strategy", diff --git a/config/src/test/java/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests.java new file mode 100644 index 0000000000..5a8b69457a --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests.java @@ -0,0 +1,385 @@ +/* + * Copyright 2002-2017 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.method; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Supplier; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.jetbrains.annotations.NotNull; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.annotation.AnnotationConfigurationException; +import org.springframework.lang.Nullable; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.access.PermissionEvaluator; +import org.springframework.security.access.annotation.BusinessService; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.authorization.AuthorizationManager; +import org.springframework.security.config.annotation.method.configuration.MethodSecurityService; +import org.springframework.security.config.test.SpringTestRule; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +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; + +/** + * @author Josh Cummings + */ +@RunWith(SpringRunner.class) +@SecurityTestExecutionListeners +public class MethodSecurityBeanDefinitionParserTests { + + private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests"; + + private final UsernamePasswordAuthenticationToken bob = new UsernamePasswordAuthenticationToken("bob", + "bobspassword"); + + @Autowired(required = false) + MethodSecurityService methodSecurityService; + + @Autowired(required = false) + BusinessService businessService; + + @Rule + public final SpringTestRule spring = new SpringTestRule(); + + @WithMockUser(roles = "ADMIN") + @Test + public void preAuthorizeWhenRoleAdminThenAccessDeniedException() { + this.spring.configLocations(xml("MethodSecurityService")).autowire(); + assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::preAuthorize) + .withMessage("Access Denied"); + } + + @WithAnonymousUser + @Test + public void preAuthorizePermitAllWhenRoleAnonymousThenPasses() { + this.spring.configLocations(xml("MethodSecurityService")).autowire(); + String result = this.methodSecurityService.preAuthorizePermitAll(); + assertThat(result).isNull(); + } + + @WithAnonymousUser + @Test + public void preAuthorizeNotAnonymousWhenRoleAnonymousThenAccessDeniedException() { + this.spring.configLocations(xml("MethodSecurityService")).autowire(); + assertThatExceptionOfType(AccessDeniedException.class) + .isThrownBy(this.methodSecurityService::preAuthorizeNotAnonymous).withMessage("Access Denied"); + } + + @WithMockUser + @Test + public void preAuthorizeNotAnonymousWhenRoleUserThenPasses() { + this.spring.configLocations(xml("MethodSecurityService")).autowire(); + this.methodSecurityService.preAuthorizeNotAnonymous(); + } + + @WithMockUser + @Test + public void securedWhenRoleUserThenAccessDeniedException() { + this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire(); + assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::secured) + .withMessage("Access Denied"); + } + + @WithMockUser(roles = "ADMIN") + @Test + public void securedWhenRoleAdminThenPasses() { + this.spring.configLocations(xml("MethodSecurityService")).autowire(); + String result = this.methodSecurityService.secured(); + assertThat(result).isNull(); + } + + @WithMockUser(roles = "ADMIN") + @Test + public void securedUserWhenRoleAdminThenAccessDeniedException() { + this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire(); + assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::securedUser) + .withMessage("Access Denied"); + } + + @WithMockUser + @Test + public void securedUserWhenRoleUserThenPasses() { + this.spring.configLocations(xml("MethodSecurityService")).autowire(); + String result = this.methodSecurityService.securedUser(); + assertThat(result).isNull(); + } + + @WithMockUser + @Test + public void preAuthorizeAdminWhenRoleUserThenAccessDeniedException() { + this.spring.configLocations(xml("MethodSecurityService")).autowire(); + assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::preAuthorizeAdmin) + .withMessage("Access Denied"); + } + + @WithMockUser(roles = "ADMIN") + @Test + public void preAuthorizeAdminWhenRoleAdminThenPasses() { + this.spring.configLocations(xml("MethodSecurityService")).autowire(); + this.methodSecurityService.preAuthorizeAdmin(); + } + + @WithMockUser(authorities = "PREFIX_ADMIN") + @Test + public void preAuthorizeAdminWhenRoleAdminAndCustomPrefixThenPasses() { + this.spring.configLocations(xml("CustomGrantedAuthorityDefaults")).autowire(); + this.methodSecurityService.preAuthorizeAdmin(); + } + + @WithMockUser + @Test + public void postHasPermissionWhenParameterIsNotGrantThenAccessDeniedException() { + this.spring.configLocations(xml("CustomPermissionEvaluator")).autowire(); + assertThatExceptionOfType(AccessDeniedException.class) + .isThrownBy(() -> this.methodSecurityService.postHasPermission("deny")).withMessage("Access Denied"); + } + + @WithMockUser + @Test + public void postHasPermissionWhenParameterIsGrantThenPasses() { + this.spring.configLocations(xml("CustomPermissionEvaluator")).autowire(); + String result = this.methodSecurityService.postHasPermission("grant"); + assertThat(result).isNull(); + } + + @WithMockUser + @Test + public void postAnnotationWhenParameterIsNotGrantThenAccessDeniedException() { + this.spring.configLocations(xml("MethodSecurityService")).autowire(); + assertThatExceptionOfType(AccessDeniedException.class) + .isThrownBy(() -> this.methodSecurityService.postAnnotation("deny")).withMessage("Access Denied"); + } + + @WithMockUser + @Test + public void postAnnotationWhenParameterIsGrantThenPasses() { + this.spring.configLocations(xml("MethodSecurityService")).autowire(); + String result = this.methodSecurityService.postAnnotation("grant"); + assertThat(result).isNull(); + } + + @WithMockUser("bob") + @Test + public void methodReturningAListWhenPrePostFiltersConfiguredThenFiltersList() { + this.spring.configLocations(xml("BusinessService")).autowire(); + List names = new ArrayList<>(); + names.add("bob"); + names.add("joe"); + names.add("sam"); + List result = this.businessService.methodReturningAList(names); + assertThat(result).hasSize(1); + assertThat(result.get(0)).isEqualTo("bob"); + } + + @WithMockUser("bob") + @Test + public void methodReturningAnArrayWhenPostFilterConfiguredThenFiltersArray() { + this.spring.configLocations(xml("BusinessService")).autowire(); + List names = new ArrayList<>(); + names.add("bob"); + names.add("joe"); + names.add("sam"); + Object[] result = this.businessService.methodReturningAnArray(names.toArray()); + assertThat(result).hasSize(1); + assertThat(result[0]).isEqualTo("bob"); + } + + @WithMockUser("bob") + @Test + public void securedUserWhenCustomBeforeAdviceConfiguredAndNameBobThenPasses() { + this.spring.configLocations(xml("CustomAuthorizationManagerBeforeAdvice")).autowire(); + String result = this.methodSecurityService.securedUser(); + assertThat(result).isNull(); + } + + @WithMockUser("joe") + @Test + public void securedUserWhenCustomBeforeAdviceConfiguredAndNameNotBobThenAccessDeniedException() { + this.spring.configLocations(xml("CustomAuthorizationManagerBeforeAdvice")).autowire(); + assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::securedUser) + .withMessage("Access Denied"); + } + + @WithMockUser("bob") + @Test + public void securedUserWhenCustomAfterAdviceConfiguredAndNameBobThenGranted() { + this.spring.configLocations(xml("CustomAuthorizationManagerAfterAdvice")).autowire(); + String result = this.methodSecurityService.securedUser(); + assertThat(result).isEqualTo("granted"); + } + + @WithMockUser("joe") + @Test + public void securedUserWhenCustomAfterAdviceConfiguredAndNameNotBobThenAccessDeniedException() { + this.spring.configLocations(xml("CustomAuthorizationManagerAfterAdvice")).autowire(); + assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::securedUser) + .withMessage("Access Denied for User 'joe'"); + } + + @WithMockUser(roles = "ADMIN") + @Test + public void jsr250WhenRoleAdminThenAccessDeniedException() { + this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire(); + assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::jsr250) + .withMessage("Access Denied"); + } + + @WithAnonymousUser + @Test + public void jsr250PermitAllWhenRoleAnonymousThenPasses() { + this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire(); + String result = this.methodSecurityService.jsr250PermitAll(); + assertThat(result).isNull(); + } + + @WithMockUser(roles = "ADMIN") + @Test + public void rolesAllowedUserWhenRoleAdminThenAccessDeniedException() { + this.spring.configLocations(xml("BusinessService")).autowire(); + assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.businessService::rolesAllowedUser) + .withMessage("Access Denied"); + } + + @WithMockUser + @Test + public void rolesAllowedUserWhenRoleUserThenPasses() { + this.spring.configLocations(xml("BusinessService")).autowire(); + this.businessService.rolesAllowedUser(); + } + + @WithMockUser(roles = { "ADMIN", "USER" }) + @Test + public void manyAnnotationsWhenMeetsConditionsThenReturnsFilteredList() throws Exception { + List names = Arrays.asList("harold", "jonathan", "pete", "bo"); + this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire(); + List filtered = this.methodSecurityService.manyAnnotations(new ArrayList<>(names)); + assertThat(filtered).hasSize(2); + assertThat(filtered).containsExactly("harold", "jonathan"); + } + + // gh-4003 + // gh-4103 + @WithMockUser + @Test + public void manyAnnotationsWhenUserThenFails() { + List names = Arrays.asList("harold", "jonathan", "pete", "bo"); + this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire(); + assertThatExceptionOfType(AccessDeniedException.class) + .isThrownBy(() -> this.methodSecurityService.manyAnnotations(new ArrayList<>(names))); + } + + @WithMockUser + @Test + public void manyAnnotationsWhenShortListThenFails() { + List names = Arrays.asList("harold", "jonathan", "pete"); + this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire(); + assertThatExceptionOfType(AccessDeniedException.class) + .isThrownBy(() -> this.methodSecurityService.manyAnnotations(new ArrayList<>(names))); + } + + @WithMockUser(roles = "ADMIN") + @Test + public void manyAnnotationsWhenAdminThenFails() { + List names = Arrays.asList("harold", "jonathan", "pete", "bo"); + this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire(); + assertThatExceptionOfType(AccessDeniedException.class) + .isThrownBy(() -> this.methodSecurityService.manyAnnotations(new ArrayList<>(names))); + } + + // gh-3183 + @Test + public void repeatedAnnotationsWhenPresentThenFails() { + this.spring.configLocations(xml("MethodSecurityService")).autowire(); + assertThatExceptionOfType(AnnotationConfigurationException.class) + .isThrownBy(() -> this.methodSecurityService.repeatedAnnotations()); + } + + // gh-3183 + @Test + public void repeatedJsr250AnnotationsWhenPresentThenFails() { + this.spring.configLocations(xml("Jsr250")).autowire(); + assertThatExceptionOfType(AnnotationConfigurationException.class) + .isThrownBy(() -> this.businessService.repeatedAnnotations()); + } + + // gh-3183 + @Test + public void repeatedSecuredAnnotationsWhenPresentThenFails() { + this.spring.configLocations(xml("Secured")).autowire(); + assertThatExceptionOfType(AnnotationConfigurationException.class) + .isThrownBy(() -> this.businessService.repeatedAnnotations()); + } + + private static String xml(String configName) { + return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; + } + + static class MyPermissionEvaluator implements 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(); + } + + } + + static class MyAuthorizationManager implements AuthorizationManager { + + @Override + public AuthorizationDecision check(Supplier authentication, MethodInvocation object) { + return new AuthorizationDecision("bob".equals(authentication.get().getName())); + } + + } + + static class MyAdvice implements MethodInterceptor { + + @Nullable + @Override + public Object invoke(@NotNull MethodInvocation invocation) { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if ("bob".equals(auth.getName())) { + return "granted"; + } + throw new AccessDeniedException("Access Denied for User '" + auth.getName() + "'"); + } + + } + +} diff --git a/config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-BusinessService.xml b/config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-BusinessService.xml new file mode 100644 index 0000000000..4e845f4b3e --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-BusinessService.xml @@ -0,0 +1,28 @@ + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-CustomAuthorizationManagerAfterAdvice.xml b/config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-CustomAuthorizationManagerAfterAdvice.xml new file mode 100644 index 0000000000..0af148c2ce --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-CustomAuthorizationManagerAfterAdvice.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-CustomAuthorizationManagerBeforeAdvice.xml b/config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-CustomAuthorizationManagerBeforeAdvice.xml new file mode 100644 index 0000000000..3887af41a9 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-CustomAuthorizationManagerBeforeAdvice.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-CustomGrantedAuthorityDefaults.xml b/config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-CustomGrantedAuthorityDefaults.xml new file mode 100644 index 0000000000..a2375a2041 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-CustomGrantedAuthorityDefaults.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-CustomPermissionEvaluator.xml b/config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-CustomPermissionEvaluator.xml new file mode 100644 index 0000000000..2e791f752f --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-CustomPermissionEvaluator.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-Jsr250.xml b/config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-Jsr250.xml new file mode 100644 index 0000000000..87891eed1b --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-Jsr250.xml @@ -0,0 +1,28 @@ + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-MethodSecurityService.xml b/config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-MethodSecurityService.xml new file mode 100644 index 0000000000..8c8bf9d941 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-MethodSecurityService.xml @@ -0,0 +1,28 @@ + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-MethodSecurityServiceEnabled.xml b/config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-MethodSecurityServiceEnabled.xml new file mode 100644 index 0000000000..364899e139 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-MethodSecurityServiceEnabled.xml @@ -0,0 +1,28 @@ + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-Secured.xml b/config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-Secured.xml new file mode 100644 index 0000000000..39df957376 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-Secured.xml @@ -0,0 +1,28 @@ + + + + + + + + + + diff --git a/core/src/main/java/org/springframework/security/access/annotation/Jsr250AuthorizationManager.java b/core/src/main/java/org/springframework/security/access/annotation/Jsr250AuthorizationManager.java deleted file mode 100644 index 19ed88ee23..0000000000 --- a/core/src/main/java/org/springframework/security/access/annotation/Jsr250AuthorizationManager.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2002-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.access.annotation; - -import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Method; -import java.util.HashSet; -import java.util.Set; -import java.util.function.Supplier; - -import javax.annotation.security.DenyAll; -import javax.annotation.security.PermitAll; -import javax.annotation.security.RolesAllowed; - -import org.aopalliance.intercept.MethodInvocation; - -import org.springframework.aop.support.AopUtils; -import org.springframework.core.annotation.AnnotatedElementUtils; -import org.springframework.lang.NonNull; -import org.springframework.security.access.method.MethodAuthorizationContext; -import org.springframework.security.authorization.AuthorityAuthorizationManager; -import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.core.Authentication; -import org.springframework.util.Assert; - -/** - * An {@link AuthorizationManager} which can determine if an {@link Authentication} has - * access to the {@link MethodInvocation} by evaluating if the {@link Authentication} - * contains a specified authority from the JSR-250 security annotations. - * - * @author Evgeniy Cheban - * @since 5.5 - */ -public final class Jsr250AuthorizationManager implements AuthorizationManager { - - private static final Set> JSR250_ANNOTATIONS = new HashSet<>(); - - static { - JSR250_ANNOTATIONS.add(DenyAll.class); - JSR250_ANNOTATIONS.add(PermitAll.class); - JSR250_ANNOTATIONS.add(RolesAllowed.class); - } - - private final Jsr250AuthorizationManagerRegistry registry = new Jsr250AuthorizationManagerRegistry(); - - private String rolePrefix = "ROLE_"; - - /** - * Sets the role prefix. Defaults to "ROLE_". - * @param rolePrefix the role prefix to use - */ - public void setRolePrefix(String rolePrefix) { - Assert.notNull(rolePrefix, "rolePrefix cannot be null"); - this.rolePrefix = rolePrefix; - } - - /** - * Determines if an {@link Authentication} has access to the {@link MethodInvocation} - * by evaluating if the {@link Authentication} contains a specified authority from the - * JSR-250 security annotations. - * @param authentication the {@link Supplier} of the {@link Authentication} to check - * @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check - * @return an {@link AuthorizationDecision} or null if the JSR-250 security - * annotations is not present - */ - @Override - public AuthorizationDecision check(Supplier authentication, - MethodAuthorizationContext methodAuthorizationContext) { - AuthorizationManager delegate = this.registry - .getManager(methodAuthorizationContext); - return delegate.check(authentication, methodAuthorizationContext); - } - - private final class Jsr250AuthorizationManagerRegistry extends AbstractAuthorizationManagerRegistry { - - @NonNull - @Override - AuthorizationManager resolveManager(Method method, Class targetClass) { - for (Annotation annotation : findJsr250Annotations(method, targetClass)) { - if (annotation instanceof DenyAll) { - return (a, o) -> new AuthorizationDecision(false); - } - if (annotation instanceof PermitAll) { - return (a, o) -> new AuthorizationDecision(true); - } - if (annotation instanceof RolesAllowed) { - RolesAllowed rolesAllowed = (RolesAllowed) annotation; - return AuthorityAuthorizationManager.hasAnyRole(Jsr250AuthorizationManager.this.rolePrefix, - rolesAllowed.value()); - } - } - return NULL_MANAGER; - } - - private Set findJsr250Annotations(Method method, Class targetClass) { - Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass); - Set annotations = findAnnotations(specificMethod); - return (annotations.isEmpty()) ? findAnnotations(specificMethod.getDeclaringClass()) : annotations; - } - - private Set findAnnotations(AnnotatedElement annotatedElement) { - return AnnotatedElementUtils.findAllMergedAnnotations(annotatedElement, JSR250_ANNOTATIONS); - } - - } - -} diff --git a/core/src/main/java/org/springframework/security/access/intercept/aopalliance/AuthorizationMethodInterceptor.java b/core/src/main/java/org/springframework/security/access/intercept/aopalliance/AuthorizationMethodInterceptor.java deleted file mode 100644 index 37de6d2df6..0000000000 --- a/core/src/main/java/org/springframework/security/access/intercept/aopalliance/AuthorizationMethodInterceptor.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2002-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.access.intercept.aopalliance; - -import org.aopalliance.intercept.MethodInterceptor; -import org.aopalliance.intercept.MethodInvocation; - -import org.springframework.aop.support.AopUtils; -import org.springframework.lang.NonNull; -import org.springframework.security.access.method.AuthorizationMethodAfterAdvice; -import org.springframework.security.access.method.AuthorizationMethodBeforeAdvice; -import org.springframework.security.access.method.MethodAuthorizationContext; -import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; - -/** - * Provides security interception of AOP Alliance based method invocations. - * - * @author Evgeniy Cheban - * @since 5.5 - */ -public final class AuthorizationMethodInterceptor implements MethodInterceptor { - - private final AuthorizationMethodBeforeAdvice beforeAdvice; - - private final AuthorizationMethodAfterAdvice afterAdvice; - - /** - * Creates an instance. - * @param beforeAdvice the {@link AuthorizationMethodBeforeAdvice} to use - * @param afterAdvice the {@link AuthorizationMethodAfterAdvice} to use - */ - public AuthorizationMethodInterceptor(AuthorizationMethodBeforeAdvice beforeAdvice, - AuthorizationMethodAfterAdvice afterAdvice) { - this.beforeAdvice = beforeAdvice; - this.afterAdvice = afterAdvice; - } - - /** - * This method should be used to enforce security on a {@link MethodInvocation}. - * @param mi the method being invoked which requires a security decision - * @return the returned value from the {@link MethodInvocation} - */ - @Override - public Object invoke(@NonNull MethodInvocation mi) throws Throwable { - MethodAuthorizationContext methodAuthorizationContext = getMethodAuthorizationContext(mi); - this.beforeAdvice.before(this::getAuthentication, methodAuthorizationContext); - Object returnedObject = mi.proceed(); - return this.afterAdvice.after(this::getAuthentication, methodAuthorizationContext, returnedObject); - } - - private MethodAuthorizationContext getMethodAuthorizationContext(MethodInvocation mi) { - Object target = mi.getThis(); - Class targetClass = (target != null) ? AopUtils.getTargetClass(target) : null; - return new MethodAuthorizationContext(mi, targetClass); - } - - private Authentication getAuthentication() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication == null) { - throw new AuthenticationCredentialsNotFoundException( - "An Authentication object was not found in the SecurityContext"); - } - return authentication; - } - -} diff --git a/core/src/main/java/org/springframework/security/access/method/AuthorizationManagerMethodAfterAdvice.java b/core/src/main/java/org/springframework/security/access/method/AuthorizationManagerMethodAfterAdvice.java deleted file mode 100644 index 776cc3e032..0000000000 --- a/core/src/main/java/org/springframework/security/access/method/AuthorizationManagerMethodAfterAdvice.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2002-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.access.method; - -import java.util.function.Supplier; - -import org.springframework.aop.MethodMatcher; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.core.Authentication; -import org.springframework.util.Assert; - -/** - * An {@link AuthorizationMethodAfterAdvice} which can determine if an - * {@link Authentication} has access to the {@link T} object using an - * {@link AuthorizationManager} if a {@link MethodMatcher} matches. - * - * @param the type of object that the authorization check is being done one. - * @author Evgeniy Cheban - * @since 5.5 - */ -public final class AuthorizationManagerMethodAfterAdvice implements AuthorizationMethodAfterAdvice { - - private final MethodMatcher methodMatcher; - - private final AuthorizationManager authorizationManager; - - /** - * Creates an instance. - * @param methodMatcher the {@link MethodMatcher} to use - * @param authorizationManager the {@link AuthorizationManager} to use - */ - public AuthorizationManagerMethodAfterAdvice(MethodMatcher methodMatcher, - AuthorizationManager authorizationManager) { - Assert.notNull(methodMatcher, "methodMatcher cannot be null"); - Assert.notNull(authorizationManager, "authorizationManager cannot be null"); - this.methodMatcher = methodMatcher; - this.authorizationManager = authorizationManager; - } - - /** - * Determines if an {@link Authentication} has access to the {@link T} object using - * the {@link AuthorizationManager}. - * @param authentication the {@link Supplier} of the {@link Authentication} to check - * @param object the {@link T} object to check - * @throws AccessDeniedException if access is not granted - */ - @Override - public Object after(Supplier authentication, T object, Object returnedObject) { - this.authorizationManager.verify(authentication, object); - return returnedObject; - } - - @Override - public MethodMatcher getMethodMatcher() { - return this.methodMatcher; - } - -} diff --git a/core/src/main/java/org/springframework/security/access/method/AuthorizationManagerMethodBeforeAdvice.java b/core/src/main/java/org/springframework/security/access/method/AuthorizationManagerMethodBeforeAdvice.java deleted file mode 100644 index e8d583d92f..0000000000 --- a/core/src/main/java/org/springframework/security/access/method/AuthorizationManagerMethodBeforeAdvice.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2002-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.access.method; - -import java.util.function.Supplier; - -import org.springframework.aop.MethodMatcher; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.core.Authentication; -import org.springframework.util.Assert; - -/** - * An {@link AuthorizationMethodBeforeAdvice} which can determine if an - * {@link Authentication} has access to the {@link T} object using an - * {@link AuthorizationManager} if a {@link MethodMatcher} matches. - * - * @param the type of object that the authorization check is being done one. - * @author Evgeniy Cheban - * @since 5.5 - */ -public final class AuthorizationManagerMethodBeforeAdvice implements AuthorizationMethodBeforeAdvice { - - private final MethodMatcher methodMatcher; - - private final AuthorizationManager authorizationManager; - - /** - * Creates an instance. - * @param methodMatcher the {@link MethodMatcher} to use - * @param authorizationManager the {@link AuthorizationManager} to use - */ - public AuthorizationManagerMethodBeforeAdvice(MethodMatcher methodMatcher, - AuthorizationManager authorizationManager) { - Assert.notNull(methodMatcher, "methodMatcher cannot be null"); - Assert.notNull(authorizationManager, "authorizationManager cannot be null"); - this.methodMatcher = methodMatcher; - this.authorizationManager = authorizationManager; - } - - /** - * Determines if an {@link Authentication} has access to the {@link T} object using - * the {@link AuthorizationManager}. - * @param authentication the {@link Supplier} of the {@link Authentication} to check - * @param object the {@link T} object to check - * @throws AccessDeniedException if access is not granted - */ - @Override - public void before(Supplier authentication, T object) { - this.authorizationManager.verify(authentication, object); - } - - @Override - public MethodMatcher getMethodMatcher() { - return this.methodMatcher; - } - -} diff --git a/core/src/main/java/org/springframework/security/access/method/AuthorizationMethodAfterAdvice.java b/core/src/main/java/org/springframework/security/access/method/AuthorizationMethodAfterAdvice.java deleted file mode 100644 index ac95eb6202..0000000000 --- a/core/src/main/java/org/springframework/security/access/method/AuthorizationMethodAfterAdvice.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2002-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.access.method; - -import java.util.function.Supplier; - -import org.aopalliance.intercept.MethodInvocation; - -import org.springframework.aop.ClassFilter; -import org.springframework.aop.Pointcut; -import org.springframework.security.core.Authentication; - -/** - * An Authorization advice that can determine if an {@link Authentication} has access to - * the returned object from the {@link MethodInvocation}. The {@link #getMethodMatcher()} - * describes when the advice applies for the method. - * - * @param the type of object that the authorization check is being done one. - * @author Evgeniy Cheban - * @since 5.5 - */ -public interface AuthorizationMethodAfterAdvice extends Pointcut { - - /** - * Returns the default {@link ClassFilter}. - * @return the {@link ClassFilter#TRUE} to use - */ - @Override - default ClassFilter getClassFilter() { - return ClassFilter.TRUE; - } - - /** - * Determines if an {@link Authentication} has access to the returned object from the - * {@link MethodInvocation}. - * @param authentication the {@link Supplier} of the {@link Authentication} to check - * @param object the {@link T} object to check - * @param returnedObject the returned object from the {@link MethodInvocation} to - * check - * @return the Object that will ultimately be returned to the caller (if - * an implementation does not wish to modify the object to be returned to the caller, - * the implementation should simply return the same object it was passed by the - * returnedObject method argument) - */ - Object after(Supplier authentication, T object, Object returnedObject); - -} diff --git a/core/src/main/java/org/springframework/security/access/method/AuthorizationMethodBeforeAdvice.java b/core/src/main/java/org/springframework/security/access/method/AuthorizationMethodBeforeAdvice.java deleted file mode 100644 index d367d52514..0000000000 --- a/core/src/main/java/org/springframework/security/access/method/AuthorizationMethodBeforeAdvice.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2002-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.access.method; - -import java.util.function.Supplier; - -import org.springframework.aop.ClassFilter; -import org.springframework.aop.Pointcut; -import org.springframework.security.core.Authentication; - -/** - * An advice which can determine if an {@link Authentication} has access to the {@link T} - * object. The {@link #getMethodMatcher()} describes when the advice applies for the - * method. - * - * @param the type of object that the authorization check is being done one. - * @author Evgeniy Cheban - * @since 5.5 - */ -public interface AuthorizationMethodBeforeAdvice extends Pointcut { - - /** - * Returns the default {@link ClassFilter}. - * @return the {@link ClassFilter#TRUE} to use - */ - @Override - default ClassFilter getClassFilter() { - return ClassFilter.TRUE; - } - - /** - * Determines if an {@link Authentication} has access to the {@link T} object. - * @param authentication the {@link Supplier} of the {@link Authentication} to check - * @param object the {@link T} object to check - */ - void before(Supplier authentication, T object); - -} diff --git a/core/src/main/java/org/springframework/security/access/method/DelegatingAuthorizationMethodAfterAdvice.java b/core/src/main/java/org/springframework/security/access/method/DelegatingAuthorizationMethodAfterAdvice.java deleted file mode 100644 index b4d6c0548d..0000000000 --- a/core/src/main/java/org/springframework/security/access/method/DelegatingAuthorizationMethodAfterAdvice.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2002-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.access.method; - -import java.lang.reflect.Method; -import java.util.List; -import java.util.function.Supplier; - -import org.aopalliance.intercept.MethodInvocation; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.aop.MethodMatcher; -import org.springframework.aop.support.StaticMethodMatcher; -import org.springframework.core.log.LogMessage; -import org.springframework.security.core.Authentication; - -/** - * An {@link AuthorizationMethodAfterAdvice} which delegates to specific - * {@link AuthorizationMethodAfterAdvice}s and returns the result (possibly modified) from - * the {@link MethodInvocation}. - * - * @author Evgeniy Cheban - * @since 5.5 - */ -public final class DelegatingAuthorizationMethodAfterAdvice - implements AuthorizationMethodAfterAdvice { - - private final Log logger = LogFactory.getLog(getClass()); - - private final MethodMatcher methodMatcher = new StaticMethodMatcher() { - @Override - public boolean matches(Method method, Class targetClass) { - for (AuthorizationMethodAfterAdvice delegate : DelegatingAuthorizationMethodAfterAdvice.this.delegates) { - MethodMatcher methodMatcher = delegate.getMethodMatcher(); - if (methodMatcher.matches(method, targetClass)) { - return true; - } - } - return false; - } - }; - - private final List> delegates; - - /** - * Creates an instance. - * @param delegates the {@link AuthorizationMethodAfterAdvice}s to use - */ - public DelegatingAuthorizationMethodAfterAdvice( - List> delegates) { - this.delegates = delegates; - } - - @Override - public MethodMatcher getMethodMatcher() { - return this.methodMatcher; - } - - /** - * Delegates to specific {@link AuthorizationMethodAfterAdvice}s and returns the - * returnedObject (possibly modified) from the method argument. - * @param authentication the {@link Supplier} of the {@link Authentication} to check - * @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check - * @param returnedObject the returned object from the {@link MethodInvocation} to - * check - * @return the returnedObject (possibly modified) from the method - * argument - */ - @Override - public Object after(Supplier authentication, MethodAuthorizationContext methodAuthorizationContext, - Object returnedObject) { - if (this.logger.isTraceEnabled()) { - this.logger.trace( - LogMessage.format("Post Authorizing %s from %s", returnedObject, methodAuthorizationContext)); - } - Object result = returnedObject; - for (AuthorizationMethodAfterAdvice delegate : this.delegates) { - if (this.logger.isTraceEnabled()) { - this.logger.trace(LogMessage.format("Checking authorization on %s from %s using %s", result, - methodAuthorizationContext, delegate)); - } - result = delegate.after(authentication, methodAuthorizationContext, result); - } - return result; - } - -} diff --git a/core/src/main/java/org/springframework/security/access/method/DelegatingAuthorizationMethodBeforeAdvice.java b/core/src/main/java/org/springframework/security/access/method/DelegatingAuthorizationMethodBeforeAdvice.java deleted file mode 100644 index ee4d4a937e..0000000000 --- a/core/src/main/java/org/springframework/security/access/method/DelegatingAuthorizationMethodBeforeAdvice.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2002-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.access.method; - -import java.lang.reflect.Method; -import java.util.List; -import java.util.function.Supplier; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.aop.MethodMatcher; -import org.springframework.aop.support.StaticMethodMatcher; -import org.springframework.core.log.LogMessage; -import org.springframework.security.core.Authentication; - -/** - * An {@link AuthorizationMethodBeforeAdvice} which delegates to a specific - * {@link AuthorizationMethodBeforeAdvice} and grants access if all - * {@link AuthorizationMethodBeforeAdvice}s granted or abstained. Denies access only if - * one of the {@link AuthorizationMethodBeforeAdvice}s denied. - * - * @author Evgeniy Cheban - * @since 5.5 - */ -public final class DelegatingAuthorizationMethodBeforeAdvice - implements AuthorizationMethodBeforeAdvice { - - private final Log logger = LogFactory.getLog(getClass()); - - private final MethodMatcher methodMatcher = new StaticMethodMatcher() { - @Override - public boolean matches(Method method, Class targetClass) { - for (AuthorizationMethodBeforeAdvice delegate : DelegatingAuthorizationMethodBeforeAdvice.this.delegates) { - MethodMatcher methodMatcher = delegate.getMethodMatcher(); - if (methodMatcher.matches(method, targetClass)) { - return true; - } - } - return false; - } - }; - - private final List> delegates; - - /** - * Creates an instance. - * @param delegates the {@link AuthorizationMethodBeforeAdvice}s to use - */ - public DelegatingAuthorizationMethodBeforeAdvice( - List> delegates) { - this.delegates = delegates; - } - - @Override - public MethodMatcher getMethodMatcher() { - return this.methodMatcher; - } - - /** - * Delegates to a specific {@link AuthorizationMethodBeforeAdvice} and grants access - * if all {@link AuthorizationMethodBeforeAdvice}s granted or abstained. Denies only - * if one of the {@link AuthorizationMethodBeforeAdvice}s denied. - * @param authentication the {@link Supplier} of the {@link Authentication} to check - * @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check - */ - @Override - public void before(Supplier authentication, MethodAuthorizationContext methodAuthorizationContext) { - if (this.logger.isTraceEnabled()) { - this.logger.trace(LogMessage.format("Pre Authorizing %s", methodAuthorizationContext)); - } - for (AuthorizationMethodBeforeAdvice delegate : this.delegates) { - if (this.logger.isTraceEnabled()) { - this.logger.trace(LogMessage.format("Checking authorization on %s using %s", methodAuthorizationContext, - delegate)); - } - delegate.before(authentication, methodAuthorizationContext); - } - } - -} diff --git a/core/src/main/java/org/springframework/security/access/method/MethodAuthorizationContext.java b/core/src/main/java/org/springframework/security/access/method/MethodAuthorizationContext.java deleted file mode 100644 index 4361f9ac9a..0000000000 --- a/core/src/main/java/org/springframework/security/access/method/MethodAuthorizationContext.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2002-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.access.method; - -import org.aopalliance.intercept.MethodInvocation; - -/** - * An authorization context which is holds the {@link MethodInvocation}, the target class - * and the returned object. - * - * @author Evgeniy Cheban - * @since 5.5 - */ -public final class MethodAuthorizationContext { - - private final MethodInvocation methodInvocation; - - private final Class targetClass; - - private Object returnObject; - - /** - * Creates an instance. - * @param methodInvocation the {@link MethodInvocation} to use - * @param targetClass the target class to use - */ - public MethodAuthorizationContext(MethodInvocation methodInvocation, Class targetClass) { - this.methodInvocation = methodInvocation; - this.targetClass = targetClass; - } - - /** - * Returns the {@link MethodInvocation}. - * @return the {@link MethodInvocation} to use - */ - public MethodInvocation getMethodInvocation() { - return this.methodInvocation; - } - - /** - * Returns the target class. - * @return the target class to use - */ - public Class getTargetClass() { - return this.targetClass; - } - - /** - * Returns the returned object from the {@link MethodInvocation}. - * @return the returned object from the {@link MethodInvocation} to use - */ - public Object getReturnObject() { - return this.returnObject; - } - - /** - * Sets the returned object from the {@link MethodInvocation}. - * @param returnObject the returned object from the {@link MethodInvocation} to use - */ - public void setReturnObject(Object returnObject) { - this.returnObject = returnObject; - } - - @Override - public String toString() { - return "MethodAuthorizationContext[methodInvocation=" + this.methodInvocation + ", targetClass=" - + this.targetClass + ", returnObject=" + this.returnObject + ']'; - } - -} diff --git a/core/src/main/java/org/springframework/security/access/annotation/AbstractAuthorizationManagerRegistry.java b/core/src/main/java/org/springframework/security/authorization/method/AbstractAuthorizationManagerRegistry.java similarity index 62% rename from core/src/main/java/org/springframework/security/access/annotation/AbstractAuthorizationManagerRegistry.java rename to core/src/main/java/org/springframework/security/authorization/method/AbstractAuthorizationManagerRegistry.java index 855f2ab0c0..d40c51c1f6 100644 --- a/core/src/main/java/org/springframework/security/access/annotation/AbstractAuthorizationManagerRegistry.java +++ b/core/src/main/java/org/springframework/security/authorization/method/AbstractAuthorizationManagerRegistry.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.security.access.annotation; +package org.springframework.security.authorization.method; import java.lang.reflect.Method; import java.util.Map; @@ -22,34 +22,31 @@ import java.util.concurrent.ConcurrentHashMap; import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.support.AopUtils; 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}. + * For internal use only, as this contract is likely to change * * @author Evgeniy Cheban - * @since 5.5 */ abstract class AbstractAuthorizationManagerRegistry { - static final AuthorizationManager NULL_MANAGER = (a, o) -> null; + static final AuthorizationManager NULL_MANAGER = (a, o) -> null; - private final Map> cachedManagers = new ConcurrentHashMap<>(); + private final Map> cachedManagers = new ConcurrentHashMap<>(); /** - * Returns an {@link AuthorizationManager} for the {@link MethodAuthorizationContext}. - * @param methodAuthorizationContext the {@link MethodAuthorizationContext} to use + * Returns an {@link AuthorizationManager} for the {@link MethodInvocation}. + * @param methodInvocation the {@link MethodInvocation} to use * @return an {@link AuthorizationManager} to use */ - final AuthorizationManager getManager( - MethodAuthorizationContext methodAuthorizationContext) { - MethodInvocation methodInvocation = methodAuthorizationContext.getMethodInvocation(); + final AuthorizationManager getManager(MethodInvocation methodInvocation) { Method method = methodInvocation.getMethod(); - Class targetClass = methodAuthorizationContext.getTargetClass(); + Object target = methodInvocation.getThis(); + Class targetClass = (target != null) ? AopUtils.getTargetClass(target) : null; MethodClassKey cacheKey = new MethodClassKey(method, targetClass); return this.cachedManagers.computeIfAbsent(cacheKey, (k) -> resolveManager(method, targetClass)); } @@ -62,6 +59,6 @@ abstract class AbstractAuthorizationManagerRegistry { * @return the non-null {@link AuthorizationManager} */ @NonNull - abstract AuthorizationManager resolveManager(Method method, Class targetClass); + abstract AuthorizationManager resolveManager(Method method, Class targetClass); } diff --git a/core/src/main/java/org/springframework/security/authorization/method/AbstractExpressionAttributeRegistry.java b/core/src/main/java/org/springframework/security/authorization/method/AbstractExpressionAttributeRegistry.java index f48d59dd33..17defe9cde 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/AbstractExpressionAttributeRegistry.java +++ b/core/src/main/java/org/springframework/security/authorization/method/AbstractExpressionAttributeRegistry.java @@ -22,30 +22,28 @@ import java.util.concurrent.ConcurrentHashMap; import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.support.AopUtils; 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}. + * For internal use only, as this contract is likely to change * * @author Evgeniy Cheban - * @since 5.5 */ abstract class AbstractExpressionAttributeRegistry { private final Map cachedAttributes = new ConcurrentHashMap<>(); /** - * Returns an {@link ExpressionAttribute} for the {@link MethodAuthorizationContext}. - * @param methodAuthorizationContext the {@link MethodAuthorizationContext} to use + * Returns an {@link ExpressionAttribute} for the {@link MethodInvocation}. + * @param mi the {@link MethodInvocation} 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(); + final T getAttribute(MethodInvocation mi) { + Method method = mi.getMethod(); + Object target = mi.getThis(); + Class targetClass = (target != null) ? AopUtils.getTargetClass(target) : null; return getAttribute(method, targetClass); } diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationAnnotationUtils.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationAnnotationUtils.java new file mode 100644 index 0000000000..582436ba17 --- /dev/null +++ b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationAnnotationUtils.java @@ -0,0 +1,113 @@ +/* + * 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.annotation.Annotation; +import java.lang.reflect.Method; + +import org.springframework.core.annotation.AnnotationConfigurationException; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.core.annotation.RepeatableContainers; + +/** + * A wrapper around {@link AnnotationUtils} that checks for, and errors on, conflicting + * annotations. This is specifically important for Spring Security annotations which are + * not designed to be repeatable. + * + * There are numerous ways that two annotations of the same type may be attached to the + * same method. For example, a class may implement a method defined in two separate + * interfaces. If both of those interfaces have a `@PreAuthorize` annotation, then it's + * unclear which `@PreAuthorize` expression Spring Security should use. + * + * Another way is when one of Spring Security's annotations is used as a meta-annotation. + * In that case, two custom annotations can be declared, each with their own + * `@PreAuthorize` declaration. If both custom annotations are used on the same method, + * then it's unclear which `@PreAuthorize` expression Spring Security should use. + * + * @author Josh Cummings + */ +final class AuthorizationAnnotationUtils { + + /** + * Perform an exhaustive search on the type hierarchy of the given {@link Method} for + * the annotation of type {@code annotationType}, including any annotations using + * {@code annotationType} as a meta-annotation. + * + * If more than one is found, then throw an error. + * @param method the method declaration to search from + * @param annotationType the annotation type to search for + * @return the unique instance of the annotation attributed to the method, + * {@code null} otherwise + * @throws AnnotationConfigurationException if more than one instance of the + * annotation is found + */ + static A findUniqueAnnotation(Method method, Class annotationType) { + MergedAnnotations mergedAnnotations = MergedAnnotations.from(method, + MergedAnnotations.SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.none()); + if (hasDuplicate(mergedAnnotations, annotationType)) { + throw new AnnotationConfigurationException("Found more than one annotation of type " + annotationType + + " attributed to " + method + + " Please remove the duplicate annotations and publish a bean to handle your authorization logic."); + } + return AnnotationUtils.findAnnotation(method, annotationType); + } + + /** + * Perform an exhaustive search on the type hierarchy of the given {@link Class} for + * the annotation of type {@code annotationType}, including any annotations using + * {@code annotationType} as a meta-annotation. + * + * If more than one is found, then throw an error. + * @param type the type to search from + * @param annotationType the annotation type to search for + * @return the unique instance of the annotation attributed to the method, + * {@code null} otherwise + * @throws AnnotationConfigurationException if more than one instance of the + * annotation is found + */ + static A findUniqueAnnotation(Class type, Class annotationType) { + MergedAnnotations mergedAnnotations = MergedAnnotations.from(type, + MergedAnnotations.SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.none()); + if (hasDuplicate(mergedAnnotations, annotationType)) { + throw new AnnotationConfigurationException("Found more than one annotation of type " + annotationType + + " attributed to " + type + + " Please remove the duplicate annotations and publish a bean to handle your authorization logic."); + } + return AnnotationUtils.findAnnotation(type, annotationType); + } + + private static boolean hasDuplicate(MergedAnnotations mergedAnnotations, + Class annotationType) { + boolean alreadyFound = false; + for (MergedAnnotation mergedAnnotation : mergedAnnotations) { + if (mergedAnnotation.getType() == annotationType) { + if (alreadyFound) { + return true; + } + alreadyFound = true; + } + } + return false; + } + + private AuthorizationAnnotationUtils() { + + } + +} diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationInterceptorsOrder.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationInterceptorsOrder.java new file mode 100644 index 0000000000..da6a26bf6e --- /dev/null +++ b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationInterceptorsOrder.java @@ -0,0 +1,71 @@ +/* + * 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.aop.Advisor; + +/** + * Ordering of Spring Security's authorization {@link Advisor}s + * + * @author Josh Cummings + * @since 5.6 + * @see PreAuthorizeAuthorizationManager + * @see PostAuthorizeAuthorizationManager + * @see SecuredAuthorizationManager + * @see Jsr250AuthorizationManager + */ +public enum AuthorizationInterceptorsOrder { + + FIRST(Integer.MIN_VALUE), + + /** + * {@link PreFilterAuthorizationMethodInterceptor} + */ + PRE_FILTER, + + PRE_AUTHORIZE, + + SECURED, + + JSR250, + + POST_AUTHORIZE, + + /** + * {@link PostFilterAuthorizationMethodInterceptor} + */ + POST_FILTER, + + LAST(Integer.MAX_VALUE); + + private static final int INTERVAL = 100; + + private final int order; + + AuthorizationInterceptorsOrder() { + this.order = ordinal() * INTERVAL; + } + + AuthorizationInterceptorsOrder(int order) { + this.order = order; + } + + public int getOrder() { + return this.order; + } + +} diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptor.java new file mode 100644 index 0000000000..cbd43c6270 --- /dev/null +++ b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptor.java @@ -0,0 +1,137 @@ +/* + * 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.function.Supplier; + +import org.aopalliance.aop.Advice; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.aop.Pointcut; +import org.springframework.aop.PointcutAdvisor; +import org.springframework.aop.framework.AopInfrastructureBean; +import org.springframework.core.Ordered; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.access.prepost.PostAuthorize; +import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; +import org.springframework.security.authorization.AuthorizationManager; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.util.Assert; + +/** + * A {@link MethodInterceptor} which can determine if an {@link Authentication} has access + * to the result of an {@link MethodInvocation} using an {@link AuthorizationManager} + * + * @author Evgeniy Cheban + * @author Josh Cummings + * @since 5.6 + */ +public final class AuthorizationManagerAfterMethodInterceptor + implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean { + + static final Supplier AUTHENTICATION_SUPPLIER = () -> { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null) { + throw new AuthenticationCredentialsNotFoundException( + "An Authentication object was not found in the SecurityContext"); + } + return authentication; + }; + + private final Pointcut pointcut; + + private final AuthorizationManager authorizationManager; + + private int order; + + /** + * Creates an instance. + * @param pointcut the {@link Pointcut} to use + * @param authorizationManager the {@link AuthorizationManager} to use + */ + public AuthorizationManagerAfterMethodInterceptor(Pointcut pointcut, + AuthorizationManager authorizationManager) { + Assert.notNull(pointcut, "pointcut cannot be null"); + Assert.notNull(authorizationManager, "authorizationManager cannot be null"); + this.pointcut = pointcut; + this.authorizationManager = authorizationManager; + } + + /** + * Creates an interceptor for the {@link PostAuthorize} annotation + * @return the interceptor + */ + public static AuthorizationManagerAfterMethodInterceptor postAuthorize() { + return postAuthorize(new PostAuthorizeAuthorizationManager()); + } + + /** + * Creates an interceptor for the {@link PostAuthorize} annotation + * @param authorizationManager the {@link PostAuthorizeAuthorizationManager} to use + * @return the interceptor + */ + public static AuthorizationManagerAfterMethodInterceptor postAuthorize( + PostAuthorizeAuthorizationManager authorizationManager) { + AuthorizationManagerAfterMethodInterceptor interceptor = new AuthorizationManagerAfterMethodInterceptor( + AuthorizationMethodPointcuts.forAnnotations(PostAuthorize.class), authorizationManager); + interceptor.setOrder(500); + return interceptor; + } + + /** + * Determine if an {@link Authentication} has access to the {@link MethodInvocation} + * using the {@link AuthorizationManager}. + * @param mi the {@link MethodInvocation} to check + * @throws AccessDeniedException if access is not granted + */ + @Override + public Object invoke(MethodInvocation mi) throws Throwable { + Object result = mi.proceed(); + this.authorizationManager.verify(AUTHENTICATION_SUPPLIER, new MethodInvocationResult(mi, result)); + return result; + } + + @Override + public int getOrder() { + return this.order; + } + + public void setOrder(int order) { + this.order = order; + } + + /** + * {@inheritDoc} + */ + @Override + public Pointcut getPointcut() { + return this.pointcut; + } + + @Override + public Advice getAdvice() { + return this; + } + + @Override + public boolean isPerInstance() { + return true; + } + +} diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.java new file mode 100644 index 0000000000..0bd1ea05ee --- /dev/null +++ b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.java @@ -0,0 +1,183 @@ +/* + * 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.function.Supplier; + +import javax.annotation.security.DenyAll; +import javax.annotation.security.PermitAll; +import javax.annotation.security.RolesAllowed; + +import org.aopalliance.aop.Advice; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.aop.Pointcut; +import org.springframework.aop.PointcutAdvisor; +import org.springframework.aop.framework.AopInfrastructureBean; +import org.springframework.core.Ordered; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.access.annotation.Secured; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; +import org.springframework.security.authorization.AuthorizationManager; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.util.Assert; + +/** + * A {@link MethodInterceptor} which uses a {@link AuthorizationManager} to determine if + * an {@link Authentication} may invoke the given {@link MethodInvocation} + * + * @author Evgeniy Cheban + * @author Josh Cummings + * @since 5.6 + */ +public final class AuthorizationManagerBeforeMethodInterceptor + implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean { + + static final Supplier AUTHENTICATION_SUPPLIER = () -> { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null) { + throw new AuthenticationCredentialsNotFoundException( + "An Authentication object was not found in the SecurityContext"); + } + return authentication; + }; + + private final Pointcut pointcut; + + private final AuthorizationManager authorizationManager; + + private int order = AuthorizationInterceptorsOrder.FIRST.getOrder(); + + /** + * Creates an instance. + * @param pointcut the {@link Pointcut} to use + * @param authorizationManager the {@link AuthorizationManager} to use + */ + public AuthorizationManagerBeforeMethodInterceptor(Pointcut pointcut, + AuthorizationManager authorizationManager) { + Assert.notNull(pointcut, "pointcut cannot be null"); + Assert.notNull(authorizationManager, "authorizationManager cannot be null"); + this.pointcut = pointcut; + this.authorizationManager = authorizationManager; + } + + /** + * Creates an interceptor for the {@link PreAuthorize} annotation + * @return the interceptor + */ + public static AuthorizationManagerBeforeMethodInterceptor preAuthorize() { + return preAuthorize(new PreAuthorizeAuthorizationManager()); + } + + /** + * Creates an interceptor for the {@link PreAuthorize} annotation + * @param authorizationManager the {@link PreAuthorizeAuthorizationManager} to use + * @return the interceptor + */ + public static AuthorizationManagerBeforeMethodInterceptor preAuthorize( + PreAuthorizeAuthorizationManager authorizationManager) { + AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor( + AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class), authorizationManager); + interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE.getOrder()); + return interceptor; + } + + /** + * Creates an interceptor for the {@link Secured} annotation + * @return the interceptor + */ + public static AuthorizationManagerBeforeMethodInterceptor secured() { + return secured(new SecuredAuthorizationManager()); + } + + /** + * Creates an interceptor for the {@link Secured} annotation + * @param authorizationManager the {@link SecuredAuthorizationManager} to use + * @return the interceptor + */ + public static AuthorizationManagerBeforeMethodInterceptor secured( + SecuredAuthorizationManager authorizationManager) { + AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor( + AuthorizationMethodPointcuts.forAnnotations(Secured.class), authorizationManager); + interceptor.setOrder(AuthorizationInterceptorsOrder.SECURED.getOrder()); + return interceptor; + } + + /** + * Creates an interceptor for the JSR-250 annotations + * @return the interceptor + */ + public static AuthorizationManagerBeforeMethodInterceptor jsr250() { + return jsr250(new Jsr250AuthorizationManager()); + } + + /** + * Creates an interceptor for the JSR-250 annotations + * @param authorizationManager the {@link Jsr250AuthorizationManager} to use + * @return the interceptor + */ + public static AuthorizationManagerBeforeMethodInterceptor jsr250(Jsr250AuthorizationManager authorizationManager) { + AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor( + AuthorizationMethodPointcuts.forAnnotations(RolesAllowed.class, DenyAll.class, PermitAll.class), + authorizationManager); + interceptor.setOrder(AuthorizationInterceptorsOrder.JSR250.getOrder()); + return interceptor; + } + + /** + * Determine if an {@link Authentication} has access to the {@link MethodInvocation} + * using the configured {@link AuthorizationManager}. + * @param mi the {@link MethodInvocation} to check + * @throws AccessDeniedException if access is not granted + */ + @Override + public Object invoke(MethodInvocation mi) throws Throwable { + this.authorizationManager.verify(AUTHENTICATION_SUPPLIER, mi); + return mi.proceed(); + } + + @Override + public int getOrder() { + return this.order; + } + + public void setOrder(int order) { + this.order = order; + } + + /** + * {@inheritDoc} + */ + @Override + public Pointcut getPointcut() { + return this.pointcut; + } + + @Override + public Advice getAdvice() { + return this; + } + + @Override + public boolean isPerInstance() { + return true; + } + +} diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationMethodPointcuts.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationMethodPointcuts.java new file mode 100644 index 0000000000..e764d95d83 --- /dev/null +++ b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationMethodPointcuts.java @@ -0,0 +1,54 @@ +/* + * 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.annotation.Annotation; + +import org.springframework.aop.Pointcut; +import org.springframework.aop.support.ComposablePointcut; +import org.springframework.aop.support.Pointcuts; +import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; + +/** + * @author Josh Cummings + */ +final class AuthorizationMethodPointcuts { + + @SafeVarargs + static Pointcut forAnnotations(Class... annotations) { + ComposablePointcut pointcut = null; + for (Class annotation : annotations) { + if (pointcut == null) { + pointcut = new ComposablePointcut(classOrMethod(annotation)); + } + else { + pointcut.union(classOrMethod(annotation)); + } + } + return pointcut; + } + + private static Pointcut classOrMethod(Class annotation) { + return Pointcuts.union(new AnnotationMatchingPointcut(null, annotation, true), + new AnnotationMatchingPointcut(annotation, true)); + } + + private AuthorizationMethodPointcuts() { + + } + +} diff --git a/core/src/main/java/org/springframework/security/authorization/method/Jsr250AuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/Jsr250AuthorizationManager.java new file mode 100644 index 0000000000..ccf8da6041 --- /dev/null +++ b/core/src/main/java/org/springframework/security/authorization/method/Jsr250AuthorizationManager.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.annotation.Annotation; +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.AnnotationConfigurationException; +import org.springframework.lang.NonNull; +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} may + * invoke the {@link MethodInvocation} by evaluating if the {@link Authentication} + * contains a specified authority from the JSR-250 security annotations. + * + * @author Evgeniy Cheban + * @author Josh Cummings + * @since 5.6 + */ +public final class Jsr250AuthorizationManager implements AuthorizationManager { + + private static final Set> JSR250_ANNOTATIONS = new HashSet<>(); + + static { + JSR250_ANNOTATIONS.add(DenyAll.class); + JSR250_ANNOTATIONS.add(PermitAll.class); + JSR250_ANNOTATIONS.add(RolesAllowed.class); + } + + private final Jsr250AuthorizationManagerRegistry registry = new Jsr250AuthorizationManagerRegistry(); + + private String rolePrefix = "ROLE_"; + + /** + * Sets the role prefix. Defaults to "ROLE_". + * @param rolePrefix the role prefix to use + */ + public void setRolePrefix(String rolePrefix) { + Assert.notNull(rolePrefix, "rolePrefix cannot be null"); + this.rolePrefix = rolePrefix; + } + + /** + * Determine if an {@link Authentication} has access to a method by evaluating the + * {@link DenyAll}, {@link PermitAll}, and {@link RolesAllowed} annotations that + * {@link MethodInvocation} specifies. + * @param authentication the {@link Supplier} of the {@link Authentication} to check + * @param methodInvocation the {@link MethodInvocation} to check + * @return an {@link AuthorizationDecision} or null if the JSR-250 security + * annotations is not present + */ + @Override + public AuthorizationDecision check(Supplier authentication, MethodInvocation methodInvocation) { + AuthorizationManager delegate = this.registry.getManager(methodInvocation); + return delegate.check(authentication, methodInvocation); + } + + private final class Jsr250AuthorizationManagerRegistry extends AbstractAuthorizationManagerRegistry { + + @NonNull + @Override + AuthorizationManager resolveManager(Method method, Class targetClass) { + Annotation annotation = findJsr250Annotation(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 Annotation findJsr250Annotation(Method method, Class targetClass) { + Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass); + Annotation annotation = findAnnotation(specificMethod); + return (annotation != null) ? annotation : findAnnotation(specificMethod.getDeclaringClass()); + } + + private Annotation findAnnotation(Method method) { + Set annotations = new HashSet<>(); + for (Class annotationClass : JSR250_ANNOTATIONS) { + Annotation annotation = AuthorizationAnnotationUtils.findUniqueAnnotation(method, annotationClass); + if (annotation != null) { + annotations.add(annotation); + } + } + if (annotations.isEmpty()) { + return null; + } + if (annotations.size() > 1) { + throw new AnnotationConfigurationException( + "The JSR-250 specification disallows DenyAll, PermitAll, and RolesAllowed from appearing on the same method."); + } + return annotations.iterator().next(); + } + + private Annotation findAnnotation(Class clazz) { + Set annotations = new HashSet<>(); + for (Class annotationClass : JSR250_ANNOTATIONS) { + Annotation annotation = AuthorizationAnnotationUtils.findUniqueAnnotation(clazz, annotationClass); + if (annotation != null) { + annotations.add(annotation); + } + } + if (annotations.isEmpty()) { + return null; + } + if (annotations.size() > 1) { + throw new AnnotationConfigurationException( + "The JSR-250 specification disallows DenyAll, PermitAll, and RolesAllowed from appearing on the same class definition."); + } + return annotations.iterator().next(); + } + + } + +} diff --git a/core/src/main/java/org/springframework/security/authorization/method/MethodInvocationResult.java b/core/src/main/java/org/springframework/security/authorization/method/MethodInvocationResult.java new file mode 100644 index 0000000000..35250f77df --- /dev/null +++ b/core/src/main/java/org/springframework/security/authorization/method/MethodInvocationResult.java @@ -0,0 +1,63 @@ +/* + * 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.aopalliance.intercept.MethodInvocation; + +import org.springframework.util.Assert; + +/** + * A context object that contains a {@link MethodInvocation} and the result of that + * {@link MethodInvocation}. + * + * @author Josh Cummings + * @since 5.6 + */ +public class MethodInvocationResult { + + private final MethodInvocation methodInvocation; + + private final Object result; + + /** + * Construct a {@link MethodInvocationResult} with the provided parameters + * @param methodInvocation the already-invoked {@link MethodInvocation} + * @param result the value returned from the {@link MethodInvocation} + */ + public MethodInvocationResult(MethodInvocation methodInvocation, Object result) { + Assert.notNull(methodInvocation, "methodInvocation cannot be null"); + this.methodInvocation = methodInvocation; + this.result = result; + } + + /** + * Return the already-invoked {@link MethodInvocation} + * @return the already-invoked {@link MethodInvocation} + */ + public MethodInvocation getMethodInvocation() { + return this.methodInvocation; + } + + /** + * Return the result of the already-invoked {@link MethodInvocation} + * @return the result + */ + public Object getResult() { + return this.result; + } + +} diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java index 11109fb67e..6988647631 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java @@ -23,13 +23,11 @@ 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; @@ -37,21 +35,21 @@ 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. + * An {@link AuthorizationManager} which can determine if an {@link Authentication} may + * return the result from an invoked {@link MethodInvocation} by evaluating an expression + * from the {@link PostAuthorize} annotation. * * @author Evgeniy Cheban - * @since 5.5 + * @since 5.6 */ -public final class PostAuthorizeAuthorizationManager implements AuthorizationManager { +public final class PostAuthorizeAuthorizationManager implements AuthorizationManager { private final PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry(); private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); /** - * Sets the {@link MethodSecurityExpressionHandler}. + * Use this the {@link MethodSecurityExpressionHandler}. * @param expressionHandler the {@link MethodSecurityExpressionHandler} to use */ public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { @@ -60,23 +58,23 @@ public final class PostAuthorizeAuthorizationManager implements AuthorizationMan } /** - * Determines if an {@link Authentication} has access to the {@link MethodInvocation} - * by evaluating an expression from the {@link PostAuthorize} annotation. + * Determine if an {@link Authentication} has access to the returned object by + * evaluating the {@link PostAuthorize} annotation that the {@link MethodInvocation} + * specifies. * @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 + * @param mi the {@link MethodInvocationResult} to check + * @return an {@link AuthorizationDecision} or {@code null} if the + * {@link PostAuthorize} annotation is not present */ @Override - public AuthorizationDecision check(Supplier authentication, - MethodAuthorizationContext methodAuthorizationContext) { - ExpressionAttribute attribute = this.registry.getAttribute(methodAuthorizationContext); + public AuthorizationDecision check(Supplier authentication, MethodInvocationResult mi) { + ExpressionAttribute attribute = this.registry.getAttribute(mi.getMethodInvocation()); if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) { return null; } EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(), - methodAuthorizationContext.getMethodInvocation()); - this.expressionHandler.setReturnObject(methodAuthorizationContext.getReturnObject(), ctx); + mi.getMethodInvocation()); + this.expressionHandler.setReturnObject(mi.getResult(), ctx); boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx); return new AuthorizationDecision(granted); } @@ -98,9 +96,10 @@ public final class PostAuthorizeAuthorizationManager implements AuthorizationMan } private PostAuthorize findPostAuthorizeAnnotation(Method method) { - PostAuthorize postAuthorize = AnnotationUtils.findAnnotation(method, PostAuthorize.class); - return (postAuthorize != null) ? postAuthorize - : AnnotationUtils.findAnnotation(method.getDeclaringClass(), PostAuthorize.class); + PostAuthorize postAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method, + PostAuthorize.class); + return (postAuthorize != null) ? postAuthorize : AuthorizationAnnotationUtils + .findUniqueAnnotation(method.getDeclaringClass(), PostAuthorize.class); } } diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodAfterAdvice.java b/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptor.java similarity index 51% rename from core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodAfterAdvice.java rename to core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptor.java index c573922c9c..737d7aa8e2 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodAfterAdvice.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptor.java @@ -19,48 +19,65 @@ package org.springframework.security.authorization.method; import java.lang.reflect.Method; import java.util.function.Supplier; +import org.aopalliance.aop.Advice; +import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; -import org.springframework.aop.MethodMatcher; +import org.springframework.aop.Pointcut; +import org.springframework.aop.PointcutAdvisor; +import org.springframework.aop.framework.AopInfrastructureBean; import org.springframework.aop.support.AopUtils; -import org.springframework.aop.support.StaticMethodMatcher; -import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.Ordered; 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.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.Assert; /** - * An {@link AuthorizationMethodAfterAdvice} which filters a returnedObject - * from the {@link MethodInvocation} by evaluating an expression from the - * {@link PostFilter} annotation. + * A {@link MethodInterceptor} which filters a {@code returnedObject} from the + * {@link MethodInvocation} by evaluating an expression from the {@link PostFilter} + * annotation. * * @author Evgeniy Cheban - * @since 5.5 + * @author Josh Cummings + * @since 5.6 */ -public final class PostFilterAuthorizationMethodAfterAdvice - implements AuthorizationMethodAfterAdvice { +public final class PostFilterAuthorizationMethodInterceptor + implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean { + + private static final Supplier AUTHENTICATION_SUPPLIER = () -> { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null) { + throw new AuthenticationCredentialsNotFoundException( + "An Authentication object was not found in the SecurityContext"); + } + return authentication; + }; 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 int order = AuthorizationInterceptorsOrder.POST_FILTER.getOrder(); + + private final Pointcut pointcut; private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); /** - * Sets the {@link MethodSecurityExpressionHandler}. + * Creates a {@link PostFilterAuthorizationMethodInterceptor} using the provided + * parameters + */ + public PostFilterAuthorizationMethodInterceptor() { + this.pointcut = AuthorizationMethodPointcuts.forAnnotations(PostFilter.class); + } + + /** + * Use this {@link MethodSecurityExpressionHandler}. * @param expressionHandler the {@link MethodSecurityExpressionHandler} to use */ public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { @@ -68,35 +85,51 @@ public final class PostFilterAuthorizationMethodAfterAdvice this.expressionHandler = expressionHandler; } + /** + * {@inheritDoc} + */ @Override - public MethodMatcher getMethodMatcher() { - return this.methodMatcher; + public int getOrder() { + return this.order; + } + + public void setOrder(int order) { + this.order = order; } /** - * Filters a returnedObject from the {@link MethodInvocation} by - * evaluating an expression from the {@link PostFilter} annotation. - * @param authentication the {@link Supplier} of the {@link Authentication} to check - * @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check - * @param returnedObject the returned object from the {@link MethodInvocation} to - * check - * @return filtered returnedObject from the {@link MethodInvocation} + * {@inheritDoc} */ @Override - public Object after(Supplier authentication, MethodAuthorizationContext methodAuthorizationContext, - Object returnedObject) { - if (returnedObject == null) { - return null; - } - ExpressionAttribute attribute = this.registry.getAttribute(methodAuthorizationContext); + public Pointcut getPointcut() { + return this.pointcut; + } + + @Override + public Advice getAdvice() { + return this; + } + + @Override + public boolean isPerInstance() { + return true; + } + + /** + * Filter a {@code returnedObject} using the {@link PostFilter} annotation that the + * {@link MethodInvocation} specifies. + * @param mi the {@link MethodInvocation} to check check + * @return filtered {@code returnedObject} + */ + @Override + public Object invoke(MethodInvocation mi) throws Throwable { + Object returnedObject = mi.proceed(); + ExpressionAttribute attribute = this.registry.getAttribute(mi); 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; + EvaluationContext ctx = this.expressionHandler.createEvaluationContext(AUTHENTICATION_SUPPLIER.get(), mi); + return this.expressionHandler.filter(returnedObject, attribute.getExpression(), ctx); } private final class PostFilterExpressionAttributeRegistry @@ -110,15 +143,15 @@ public final class PostFilterAuthorizationMethodAfterAdvice if (postFilter == null) { return ExpressionAttribute.NULL_ATTRIBUTE; } - Expression postFilterExpression = PostFilterAuthorizationMethodAfterAdvice.this.expressionHandler + Expression postFilterExpression = PostFilterAuthorizationMethodInterceptor.this.expressionHandler .getExpressionParser().parseExpression(postFilter.value()); return new ExpressionAttribute(postFilterExpression); } private PostFilter findPostFilterAnnotation(Method method) { - PostFilter postFilter = AnnotationUtils.findAnnotation(method, PostFilter.class); + PostFilter postFilter = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PostFilter.class); return (postFilter != null) ? postFilter - : AnnotationUtils.findAnnotation(method.getDeclaringClass(), PostFilter.class); + : AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PostFilter.class); } } diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java index bebceee0e6..c7f0f9523d 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java @@ -23,13 +23,11 @@ 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; @@ -37,14 +35,14 @@ 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 + * An {@link AuthorizationManager} which can determine if an {@link Authentication} may + * invoke the {@link MethodInvocation} by evaluating an expression from the * {@link PreAuthorize} annotation. * * @author Evgeniy Cheban - * @since 5.5 + * @since 5.6 */ -public final class PreAuthorizeAuthorizationManager implements AuthorizationManager { +public final class PreAuthorizeAuthorizationManager implements AuthorizationManager { private final PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry(); @@ -60,22 +58,21 @@ public final class PreAuthorizeAuthorizationManager implements AuthorizationMana } /** - * Determines if an {@link Authentication} has access to the {@link MethodInvocation} - * by evaluating an expression from the {@link PreAuthorize} annotation. + * Determine if an {@link Authentication} has access to a method by evaluating an + * expression from the {@link PreAuthorize} annotation that the + * {@link MethodInvocation} specifies. * @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 + * @param mi the {@link MethodInvocation} to check + * @return an {@link AuthorizationDecision} or {@code null} if the + * {@link PreAuthorize} annotation is not present */ @Override - public AuthorizationDecision check(Supplier authentication, - MethodAuthorizationContext methodAuthorizationContext) { - ExpressionAttribute attribute = this.registry.getAttribute(methodAuthorizationContext); + public AuthorizationDecision check(Supplier authentication, MethodInvocation mi) { + ExpressionAttribute attribute = this.registry.getAttribute(mi); if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) { return null; } - EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(), - methodAuthorizationContext.getMethodInvocation()); + EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(), mi); boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx); return new AuthorizationDecision(granted); } @@ -97,9 +94,9 @@ public final class PreAuthorizeAuthorizationManager implements AuthorizationMana } private PreAuthorize findPreAuthorizeAnnotation(Method method) { - PreAuthorize preAuthorize = AnnotationUtils.findAnnotation(method, PreAuthorize.class); + PreAuthorize preAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class); return (preAuthorize != null) ? preAuthorize - : AnnotationUtils.findAnnotation(method.getDeclaringClass(), PreAuthorize.class); + : AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PreAuthorize.class); } } diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodBeforeAdvice.java b/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptor.java similarity index 64% rename from core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodBeforeAdvice.java rename to core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptor.java index 93ab362103..d9cfc29dee 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodBeforeAdvice.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptor.java @@ -19,48 +19,65 @@ package org.springframework.security.authorization.method; import java.lang.reflect.Method; import java.util.function.Supplier; +import org.aopalliance.aop.Advice; +import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; -import org.springframework.aop.MethodMatcher; +import org.springframework.aop.Pointcut; +import org.springframework.aop.PointcutAdvisor; +import org.springframework.aop.framework.AopInfrastructureBean; import org.springframework.aop.support.AopUtils; -import org.springframework.aop.support.StaticMethodMatcher; -import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.Ordered; 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.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; 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. + * A {@link MethodInterceptor} which filters a method argument by evaluating an expression + * from the {@link PreFilter} annotation. * * @author Evgeniy Cheban - * @since 5.5 + * @author Josh Cummings + * @since 5.6 */ -public final class PreFilterAuthorizationMethodBeforeAdvice - implements AuthorizationMethodBeforeAdvice { +public final class PreFilterAuthorizationMethodInterceptor + implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean { + + private static final Supplier AUTHENTICATION_SUPPLIER = () -> { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null) { + throw new AuthenticationCredentialsNotFoundException( + "An Authentication object was not found in the SecurityContext"); + } + return authentication; + }; 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 int order = AuthorizationInterceptorsOrder.PRE_FILTER.getOrder(); + + private final Pointcut pointcut; private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); /** - * Sets the {@link MethodSecurityExpressionHandler}. + * Creates a {@link PreFilterAuthorizationMethodInterceptor} using the provided + * parameters + */ + public PreFilterAuthorizationMethodInterceptor() { + this.pointcut = AuthorizationMethodPointcuts.forAnnotations(PreFilter.class); + } + + /** + * Use this {@link MethodSecurityExpressionHandler} * @param expressionHandler the {@link MethodSecurityExpressionHandler} to use */ public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { @@ -68,27 +85,51 @@ public final class PreFilterAuthorizationMethodBeforeAdvice this.expressionHandler = expressionHandler; } + /** + * {@inheritDoc} + */ @Override - public MethodMatcher getMethodMatcher() { - return this.methodMatcher; + public int getOrder() { + return this.order; + } + + public void setOrder(int order) { + this.order = order; } /** - * 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 + * {@inheritDoc} */ @Override - public void before(Supplier authentication, MethodAuthorizationContext methodAuthorizationContext) { - PreFilterExpressionAttribute attribute = this.registry.getAttribute(methodAuthorizationContext); + public Pointcut getPointcut() { + return this.pointcut; + } + + @Override + public Advice getAdvice() { + return this; + } + + @Override + public boolean isPerInstance() { + return true; + } + + /** + * Filter the method argument specified in the {@link PreFilter} annotation that + * {@link MethodInvocation} specifies. + * @param mi the {@link MethodInvocation} to check + */ + @Override + public Object invoke(MethodInvocation mi) throws Throwable { + PreFilterExpressionAttribute attribute = this.registry.getAttribute(mi); if (attribute == PreFilterExpressionAttribute.NULL_ATTRIBUTE) { - return; + return mi.proceed(); } - MethodInvocation mi = methodAuthorizationContext.getMethodInvocation(); - EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(), mi); + EvaluationContext ctx = this.expressionHandler.createEvaluationContext(AUTHENTICATION_SUPPLIER.get(), mi); Object filterTarget = findFilterTarget(attribute.filterTarget, ctx, mi); this.expressionHandler.filter(filterTarget, attribute.getExpression(), ctx); + return mi.proceed(); } private Object findFilterTarget(String filterTargetName, EvaluationContext ctx, MethodInvocation methodInvocation) { @@ -122,15 +163,15 @@ public final class PreFilterAuthorizationMethodBeforeAdvice if (preFilter == null) { return PreFilterExpressionAttribute.NULL_ATTRIBUTE; } - Expression preFilterExpression = PreFilterAuthorizationMethodBeforeAdvice.this.expressionHandler + Expression preFilterExpression = PreFilterAuthorizationMethodInterceptor.this.expressionHandler .getExpressionParser().parseExpression(preFilter.value()); return new PreFilterExpressionAttribute(preFilterExpression, preFilter.filterTarget()); } private PreFilter findPreFilterAnnotation(Method method) { - PreFilter preFilter = AnnotationUtils.findAnnotation(method, PreFilter.class); + PreFilter preFilter = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreFilter.class); return (preFilter != null) ? preFilter - : AnnotationUtils.findAnnotation(method.getDeclaringClass(), PreFilter.class); + : AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PreFilter.class); } } diff --git a/core/src/main/java/org/springframework/security/access/annotation/SecuredAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/SecuredAuthorizationManager.java similarity index 65% rename from core/src/main/java/org/springframework/security/access/annotation/SecuredAuthorizationManager.java rename to core/src/main/java/org/springframework/security/authorization/method/SecuredAuthorizationManager.java index ca962669f2..73ad02ce38 100644 --- a/core/src/main/java/org/springframework/security/access/annotation/SecuredAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/SecuredAuthorizationManager.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.security.access.annotation; +package org.springframework.security.authorization.method; import java.lang.reflect.Method; import java.util.function.Supplier; @@ -22,57 +22,53 @@ 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.access.annotation.Secured; 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} + * An {@link AuthorizationManager} which can determine if an {@link Authentication} may + * invoke 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 + * @since 5.6 */ -public final class SecuredAuthorizationManager implements AuthorizationManager { +public final class SecuredAuthorizationManager implements AuthorizationManager { private final SecuredAuthorizationManagerRegistry registry = new SecuredAuthorizationManagerRegistry(); /** - * Determines if an {@link Authentication} has access to the {@link MethodInvocation} - * by evaluating if the {@link Authentication} contains a specified authority from the - * Spring Security's {@link Secured} annotation. + * Determine if an {@link Authentication} has access to a method by evaluating the + * {@link Secured} annotation that {@link MethodInvocation} specifies. * @param authentication the {@link Supplier} of the {@link Authentication} to check - * @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check + * @param mi the {@link MethodInvocation} to check * @return an {@link AuthorizationDecision} or null if the {@link Secured} annotation * is not present */ @Override - public AuthorizationDecision check(Supplier authentication, - MethodAuthorizationContext methodAuthorizationContext) { - AuthorizationManager delegate = this.registry - .getManager(methodAuthorizationContext); - return delegate.check(authentication, methodAuthorizationContext); + public AuthorizationDecision check(Supplier authentication, MethodInvocation mi) { + AuthorizationManager delegate = this.registry.getManager(mi); + return delegate.check(authentication, mi); } private static final class SecuredAuthorizationManagerRegistry extends AbstractAuthorizationManagerRegistry { @NonNull @Override - AuthorizationManager resolveManager(Method method, Class targetClass) { + AuthorizationManager resolveManager(Method method, Class targetClass) { Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass); Secured secured = findSecuredAnnotation(specificMethod); return (secured != null) ? AuthorityAuthorizationManager.hasAnyAuthority(secured.value()) : NULL_MANAGER; } private Secured findSecuredAnnotation(Method method) { - Secured secured = AnnotationUtils.findAnnotation(method, Secured.class); + Secured secured = AuthorizationAnnotationUtils.findUniqueAnnotation(method, Secured.class); return (secured != null) ? secured - : AnnotationUtils.findAnnotation(method.getDeclaringClass(), Secured.class); + : AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), Secured.class); } } diff --git a/core/src/test/java/org/springframework/security/access/annotation/BusinessService.java b/core/src/test/java/org/springframework/security/access/annotation/BusinessService.java index 16c63b7e28..7d42e18f3c 100644 --- a/core/src/test/java/org/springframework/security/access/annotation/BusinessService.java +++ b/core/src/test/java/org/springframework/security/access/annotation/BusinessService.java @@ -60,4 +60,10 @@ public interface BusinessService extends Serializable { List methodReturningAList(String userName, String extraParam); + @RequireAdminRole + @RequireUserRole + default void repeatedAnnotations() { + + } + } diff --git a/core/src/test/java/org/springframework/security/access/annotation/Jsr250AuthorizationManagerTests.java b/core/src/test/java/org/springframework/security/access/annotation/Jsr250AuthorizationManagerTests.java deleted file mode 100644 index a8cdf3273b..0000000000 --- a/core/src/test/java/org/springframework/security/access/annotation/Jsr250AuthorizationManagerTests.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright 2002-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.access.annotation; - -import java.util.function.Supplier; - -import javax.annotation.security.DenyAll; -import javax.annotation.security.PermitAll; -import javax.annotation.security.RolesAllowed; - -import org.junit.Test; - -import org.springframework.security.access.intercept.method.MockMethodInvocation; -import org.springframework.security.access.method.MethodAuthorizationContext; -import org.springframework.security.authentication.TestAuthentication; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.core.Authentication; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests for {@link Jsr250AuthorizationManager}. - * - * @author Evgeniy Cheban - */ -public class Jsr250AuthorizationManagerTests { - - @Test - public void rolePrefixWhenNotSetThenDefaultsToRole() { - Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); - assertThat(manager).extracting("rolePrefix").isEqualTo("ROLE_"); - } - - @Test - public void setRolePrefixWhenNullThenException() { - Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); - assertThatIllegalArgumentException().isThrownBy(() -> manager.setRolePrefix(null)) - .withMessage("rolePrefix cannot be null"); - } - - @Test - public void setRolePrefixWhenNotNullThenSets() { - Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); - manager.setRolePrefix("CUSTOM_"); - assertThat(manager).extracting("rolePrefix").isEqualTo("CUSTOM_"); - } - - @Test - public void checkDoSomethingWhenNoJsr250AnnotationsThenNullDecision() throws Exception { - MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, - "doSomething"); - MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation, - TestClass.class); - Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); - AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, - methodAuthorizationContext); - assertThat(decision).isNull(); - } - - @Test - public void checkPermitAllRolesAllowedAdminWhenRoleUserThenGrantedDecision() throws Exception { - MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, - "permitAllRolesAllowedAdmin"); - MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation, - TestClass.class); - Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); - AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, - methodAuthorizationContext); - assertThat(decision).isNotNull(); - assertThat(decision.isGranted()).isTrue(); - } - - @Test - public void checkDenyAllRolesAllowedAdminWhenRoleAdminThenDeniedDecision() throws Exception { - MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, - "denyAllRolesAllowedAdmin"); - MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation, - TestClass.class); - Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); - AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedAdmin, - methodAuthorizationContext); - assertThat(decision).isNotNull(); - assertThat(decision.isGranted()).isFalse(); - } - - @Test - public void checkRolesAllowedUserOrAdminWhenRoleUserThenGrantedDecision() throws Exception { - MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, - "rolesAllowedUserOrAdmin"); - MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation, - TestClass.class); - Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); - AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, - methodAuthorizationContext); - assertThat(decision).isNotNull(); - assertThat(decision.isGranted()).isTrue(); - } - - @Test - public void checkRolesAllowedUserOrAdminWhenRoleAdminThenGrantedDecision() throws Exception { - MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, - "rolesAllowedUserOrAdmin"); - MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation, - TestClass.class); - Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); - AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedAdmin, - methodAuthorizationContext); - assertThat(decision).isNotNull(); - assertThat(decision.isGranted()).isTrue(); - } - - @Test - public void checkRolesAllowedUserOrAdminWhenRoleAnonymousThenDeniedDecision() throws Exception { - Supplier authentication = () -> new TestingAuthenticationToken("user", "password", - "ROLE_ANONYMOUS"); - MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, - "rolesAllowedUserOrAdmin"); - MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation, - TestClass.class); - Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); - AuthorizationDecision decision = manager.check(authentication, methodAuthorizationContext); - assertThat(decision).isNotNull(); - assertThat(decision.isGranted()).isFalse(); - } - - public static class TestClass { - - public void doSomething() { - - } - - @DenyAll - @RolesAllowed("ADMIN") - public void denyAllRolesAllowedAdmin() { - - } - - @PermitAll - @RolesAllowed("ADMIN") - public void permitAllRolesAllowedAdmin() { - - } - - @RolesAllowed({ "USER", "ADMIN" }) - public void rolesAllowedUserOrAdmin() { - - } - - } - -} diff --git a/core/src/test/java/org/springframework/security/access/annotation/RequireAdminRole.java b/core/src/test/java/org/springframework/security/access/annotation/RequireAdminRole.java new file mode 100644 index 0000000000..70e68eda6e --- /dev/null +++ b/core/src/test/java/org/springframework/security/access/annotation/RequireAdminRole.java @@ -0,0 +1,32 @@ +/* + * 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.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.annotation.security.RolesAllowed; + +import org.springframework.security.access.prepost.PreAuthorize; + +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasRole('ADMIN')") +@RolesAllowed("ADMIN") +@Secured("ADMIN") +public @interface RequireAdminRole { + +} diff --git a/core/src/test/java/org/springframework/security/access/annotation/RequireUserRole.java b/core/src/test/java/org/springframework/security/access/annotation/RequireUserRole.java new file mode 100644 index 0000000000..a11b7fc6de --- /dev/null +++ b/core/src/test/java/org/springframework/security/access/annotation/RequireUserRole.java @@ -0,0 +1,32 @@ +/* + * 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.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.annotation.security.RolesAllowed; + +import org.springframework.security.access.prepost.PreAuthorize; + +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasRole('USER')") +@RolesAllowed("ADMIN") +@Secured("USER") +public @interface RequireUserRole { + +} diff --git a/core/src/test/java/org/springframework/security/access/annotation/SecuredAuthorizationManagerTests.java b/core/src/test/java/org/springframework/security/access/annotation/SecuredAuthorizationManagerTests.java deleted file mode 100644 index 0d94919219..0000000000 --- a/core/src/test/java/org/springframework/security/access/annotation/SecuredAuthorizationManagerTests.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2002-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.access.annotation; - -import java.util.function.Supplier; - -import org.junit.Test; - -import org.springframework.security.access.intercept.method.MockMethodInvocation; -import org.springframework.security.access.method.MethodAuthorizationContext; -import org.springframework.security.authentication.TestAuthentication; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.core.Authentication; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link SecuredAuthorizationManager}. - * - * @author Evgeniy Cheban - */ -public class SecuredAuthorizationManagerTests { - - @Test - public void checkDoSomethingWhenNoSecuredAnnotationThenNullDecision() throws Exception { - MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, - "doSomething"); - MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation, - TestClass.class); - SecuredAuthorizationManager manager = new SecuredAuthorizationManager(); - AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, - methodAuthorizationContext); - assertThat(decision).isNull(); - } - - @Test - public void checkSecuredUserOrAdminWhenRoleUserThenGrantedDecision() throws Exception { - MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, - "securedUserOrAdmin"); - MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation, - TestClass.class); - SecuredAuthorizationManager manager = new SecuredAuthorizationManager(); - AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, - methodAuthorizationContext); - assertThat(decision).isNotNull(); - assertThat(decision.isGranted()).isTrue(); - } - - @Test - public void checkSecuredUserOrAdminWhenRoleAdminThenGrantedDecision() throws Exception { - MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, - "securedUserOrAdmin"); - MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation, - TestClass.class); - SecuredAuthorizationManager manager = new SecuredAuthorizationManager(); - AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedAdmin, - methodAuthorizationContext); - assertThat(decision).isNotNull(); - assertThat(decision.isGranted()).isTrue(); - } - - @Test - public void checkSecuredUserOrAdminWhenRoleAnonymousThenDeniedDecision() throws Exception { - Supplier authentication = () -> new TestingAuthenticationToken("user", "password", - "ROLE_ANONYMOUS"); - MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, - "securedUserOrAdmin"); - MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation, - TestClass.class); - SecuredAuthorizationManager manager = new SecuredAuthorizationManager(); - AuthorizationDecision decision = manager.check(authentication, methodAuthorizationContext); - assertThat(decision).isNotNull(); - assertThat(decision.isGranted()).isFalse(); - } - - public static class TestClass { - - public void doSomething() { - - } - - @Secured({ "ROLE_USER", "ROLE_ADMIN" }) - public void securedUserOrAdmin() { - - } - - } - -} diff --git a/core/src/test/java/org/springframework/security/access/intercept/aopalliance/AuthorizationMethodInterceptorTests.java b/core/src/test/java/org/springframework/security/access/intercept/aopalliance/AuthorizationMethodInterceptorTests.java deleted file mode 100644 index 3f647cc9bf..0000000000 --- a/core/src/test/java/org/springframework/security/access/intercept/aopalliance/AuthorizationMethodInterceptorTests.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2002-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.access.intercept.aopalliance; - -import java.util.function.Supplier; - -import org.junit.After; -import org.junit.Test; - -import org.springframework.aop.MethodMatcher; -import org.springframework.security.access.intercept.method.MockMethodInvocation; -import org.springframework.security.access.method.AuthorizationMethodAfterAdvice; -import org.springframework.security.access.method.AuthorizationMethodBeforeAdvice; -import org.springframework.security.access.method.MethodAuthorizationContext; -import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; -import org.springframework.security.authentication.TestAuthentication; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.context.SecurityContextImpl; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; - -/** - * Tests for {@link AuthorizationMethodInterceptor}. - * - * @author Evgeniy Cheban - */ -public class AuthorizationMethodInterceptorTests { - - @After - public void tearDown() { - SecurityContextHolder.clearContext(); - } - - @Test - public void invokeWhenAuthenticatedThenVerifyAdvicesUsage() throws Throwable { - Authentication authentication = TestAuthentication.authenticatedUser(); - SecurityContextHolder.setContext(new SecurityContextImpl(authentication)); - MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, - "doSomethingString"); - AuthorizationMethodBeforeAdvice mockBeforeAdvice = mock( - AuthorizationMethodBeforeAdvice.class); - AuthorizationMethodAfterAdvice mockAfterAdvice = mock( - AuthorizationMethodAfterAdvice.class); - given(mockAfterAdvice.after(any(), any(MethodAuthorizationContext.class), eq(null))).willReturn("abc"); - AuthorizationMethodInterceptor interceptor = new AuthorizationMethodInterceptor(mockBeforeAdvice, - mockAfterAdvice); - Object result = interceptor.invoke(mockMethodInvocation); - assertThat(result).isEqualTo("abc"); - verify(mockAfterAdvice).after(any(), any(MethodAuthorizationContext.class), eq(null)); - } - - @Test - public void invokeWhenNotAuthenticatedThenAuthenticationCredentialsNotFoundException() throws Exception { - MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, - "doSomethingString"); - AuthorizationMethodBeforeAdvice beforeAdvice = new AuthorizationMethodBeforeAdvice() { - @Override - public MethodMatcher getMethodMatcher() { - return MethodMatcher.TRUE; - } - - @Override - public void before(Supplier authentication, - MethodAuthorizationContext methodAuthorizationContext) { - authentication.get(); - } - }; - AuthorizationMethodAfterAdvice mockAfterAdvice = mock( - AuthorizationMethodAfterAdvice.class); - AuthorizationMethodInterceptor interceptor = new AuthorizationMethodInterceptor(beforeAdvice, mockAfterAdvice); - assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) - .isThrownBy(() -> interceptor.invoke(mockMethodInvocation)) - .withMessage("An Authentication object was not found in the SecurityContext"); - verifyNoInteractions(mockAfterAdvice); - } - - public static class TestClass { - - public String doSomethingString() { - return null; - } - - } - -} diff --git a/core/src/test/java/org/springframework/security/access/method/DelegatingAuthorizationMethodAfterAdviceTests.java b/core/src/test/java/org/springframework/security/access/method/DelegatingAuthorizationMethodAfterAdviceTests.java deleted file mode 100644 index 79bb042457..0000000000 --- a/core/src/test/java/org/springframework/security/access/method/DelegatingAuthorizationMethodAfterAdviceTests.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright 2002-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.access.method; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Supplier; - -import org.junit.Test; - -import org.springframework.aop.MethodMatcher; -import org.springframework.aop.support.StaticMethodMatcher; -import org.springframework.security.access.intercept.method.MockMethodInvocation; -import org.springframework.security.authentication.TestAuthentication; -import org.springframework.security.core.Authentication; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link DelegatingAuthorizationMethodAfterAdvice}. - * - * @author Evgeniy Cheban - */ -public class DelegatingAuthorizationMethodAfterAdviceTests { - - @Test - public void methodMatcherWhenNoneMatchesThenNotMatches() throws Exception { - List> delegates = new ArrayList<>(); - delegates.add(new AuthorizationMethodAfterAdvice() { - @Override - public Object after(Supplier authentication, MethodAuthorizationContext object, - Object returnedObject) { - return returnedObject; - } - - @Override - public MethodMatcher getMethodMatcher() { - return new StaticMethodMatcher() { - @Override - public boolean matches(Method method, Class targetClass) { - return false; - } - }; - } - }); - delegates.add(new AuthorizationMethodAfterAdvice() { - @Override - public Object after(Supplier authentication, MethodAuthorizationContext object, - Object returnedObject) { - return returnedObject; - } - - @Override - public MethodMatcher getMethodMatcher() { - return new StaticMethodMatcher() { - @Override - public boolean matches(Method method, Class targetClass) { - return false; - } - }; - } - }); - DelegatingAuthorizationMethodAfterAdvice advice = new DelegatingAuthorizationMethodAfterAdvice(delegates); - MethodMatcher methodMatcher = advice.getMethodMatcher(); - assertThat(methodMatcher.matches(TestClass.class.getMethod("doSomething"), TestClass.class)).isFalse(); - } - - @Test - public void methodMatcherWhenAnyMatchesThenMatches() throws Exception { - List> delegates = new ArrayList<>(); - delegates.add(new AuthorizationMethodAfterAdvice() { - @Override - public Object after(Supplier authentication, MethodAuthorizationContext object, - Object returnedObject) { - return returnedObject; - } - - @Override - public MethodMatcher getMethodMatcher() { - return new StaticMethodMatcher() { - @Override - public boolean matches(Method method, Class targetClass) { - return false; - } - }; - } - }); - delegates.add(new AuthorizationMethodAfterAdvice() { - @Override - public Object after(Supplier authentication, MethodAuthorizationContext object, - Object returnedObject) { - return returnedObject; - } - - @Override - public MethodMatcher getMethodMatcher() { - return MethodMatcher.TRUE; - } - }); - DelegatingAuthorizationMethodAfterAdvice advice = new DelegatingAuthorizationMethodAfterAdvice(delegates); - MethodMatcher methodMatcher = advice.getMethodMatcher(); - assertThat(methodMatcher.matches(TestClass.class.getMethod("doSomething"), TestClass.class)).isTrue(); - } - - @Test - public void checkWhenDelegatingAdviceModifiesReturnedObjectThenModifiedReturnedObject() throws Exception { - List> delegates = new ArrayList<>(); - delegates.add(new AuthorizationMethodAfterAdvice() { - @Override - public Object after(Supplier authentication, MethodAuthorizationContext object, - Object returnedObject) { - return returnedObject + "b"; - } - - @Override - public MethodMatcher getMethodMatcher() { - return MethodMatcher.TRUE; - } - }); - delegates.add(new AuthorizationMethodAfterAdvice() { - @Override - public Object after(Supplier authentication, MethodAuthorizationContext object, - Object returnedObject) { - return returnedObject + "c"; - } - - @Override - public MethodMatcher getMethodMatcher() { - return MethodMatcher.TRUE; - } - }); - MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, - "doSomething"); - MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation, - TestClass.class); - DelegatingAuthorizationMethodAfterAdvice advice = new DelegatingAuthorizationMethodAfterAdvice(delegates); - Object result = advice.after(TestAuthentication::authenticatedUser, methodAuthorizationContext, "a"); - assertThat(result).isEqualTo("abc"); - } - - public static class TestClass { - - public String doSomething() { - return null; - } - - } - -} diff --git a/core/src/test/java/org/springframework/security/access/method/DelegatingAuthorizationMethodBeforeAdviceTests.java b/core/src/test/java/org/springframework/security/access/method/DelegatingAuthorizationMethodBeforeAdviceTests.java deleted file mode 100644 index 5e82783f7e..0000000000 --- a/core/src/test/java/org/springframework/security/access/method/DelegatingAuthorizationMethodBeforeAdviceTests.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright 2002-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.access.method; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.function.Supplier; - -import org.junit.Test; - -import org.springframework.aop.MethodMatcher; -import org.springframework.aop.support.StaticMethodMatcher; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.intercept.method.MockMethodInvocation; -import org.springframework.security.authentication.TestAuthentication; -import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.core.Authentication; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * Tests for {@link DelegatingAuthorizationMethodBeforeAdvice}. - * - * @author Evgeniy Cheban - */ -public class DelegatingAuthorizationMethodBeforeAdviceTests { - - @Test - public void methodMatcherWhenNoneMatchesThenNotMatches() throws Exception { - List> delegates = new ArrayList<>(); - delegates.add(new AuthorizationMethodBeforeAdvice() { - @Override - public MethodMatcher getMethodMatcher() { - return new StaticMethodMatcher() { - @Override - public boolean matches(Method method, Class targetClass) { - return false; - } - }; - } - - @Override - public void before(Supplier authentication, MethodAuthorizationContext object) { - } - }); - delegates.add(new AuthorizationMethodBeforeAdvice() { - @Override - public MethodMatcher getMethodMatcher() { - return new StaticMethodMatcher() { - @Override - public boolean matches(Method method, Class targetClass) { - return false; - } - }; - } - - @Override - public void before(Supplier authentication, MethodAuthorizationContext object) { - } - }); - DelegatingAuthorizationMethodBeforeAdvice advice = new DelegatingAuthorizationMethodBeforeAdvice(delegates); - MethodMatcher methodMatcher = advice.getMethodMatcher(); - assertThat(methodMatcher.matches(TestClass.class.getMethod("doSomething"), TestClass.class)).isFalse(); - } - - @Test - public void methodMatcherWhenAnyMatchesThenMatches() throws Exception { - List> delegates = new ArrayList<>(); - delegates.add(new AuthorizationMethodBeforeAdvice() { - @Override - public MethodMatcher getMethodMatcher() { - return new StaticMethodMatcher() { - @Override - public boolean matches(Method method, Class targetClass) { - return false; - } - }; - } - - @Override - public void before(Supplier authentication, MethodAuthorizationContext object) { - } - }); - delegates.add(new AuthorizationMethodBeforeAdvice() { - @Override - public MethodMatcher getMethodMatcher() { - return MethodMatcher.TRUE; - } - - @Override - public void before(Supplier authentication, MethodAuthorizationContext object) { - } - }); - DelegatingAuthorizationMethodBeforeAdvice advice = new DelegatingAuthorizationMethodBeforeAdvice(delegates); - MethodMatcher methodMatcher = advice.getMethodMatcher(); - assertThat(methodMatcher.matches(TestClass.class.getMethod("doSomething"), TestClass.class)).isTrue(); - } - - @Test - public void checkWhenAllGrantsOrAbstainsThenPasses() throws Exception { - List> delegates = new ArrayList<>(); - delegates.add(new AuthorizationManagerMethodBeforeAdvice<>(MethodMatcher.TRUE, (a, o) -> null)); - delegates.add(new AuthorizationManagerMethodBeforeAdvice<>(MethodMatcher.TRUE, - (a, o) -> new AuthorizationDecision(true))); - delegates.add(new AuthorizationManagerMethodBeforeAdvice<>(MethodMatcher.TRUE, (a, o) -> null)); - DelegatingAuthorizationMethodBeforeAdvice advice = new DelegatingAuthorizationMethodBeforeAdvice(delegates); - MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, - "doSomething"); - MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation, - TestClass.class); - advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext); - } - - @Test - public void checkWhenAnyDeniesThenAccessDeniedException() throws Exception { - List> delegates = new ArrayList<>(); - delegates.add(new AuthorizationManagerMethodBeforeAdvice<>(MethodMatcher.TRUE, (a, o) -> null)); - delegates.add(new AuthorizationManagerMethodBeforeAdvice<>(MethodMatcher.TRUE, - (a, o) -> new AuthorizationDecision(true))); - delegates.add(new AuthorizationManagerMethodBeforeAdvice<>(MethodMatcher.TRUE, - (a, o) -> new AuthorizationDecision(false))); - DelegatingAuthorizationMethodBeforeAdvice advice = new DelegatingAuthorizationMethodBeforeAdvice(delegates); - MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, - "doSomething"); - MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation, - TestClass.class); - assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(() -> advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext)) - .withMessage("Access Denied"); - } - - @Test - public void checkWhenDelegatesEmptyThenPasses() throws Exception { - DelegatingAuthorizationMethodBeforeAdvice advice = new DelegatingAuthorizationMethodBeforeAdvice( - Collections.emptyList()); - MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, - "doSomething"); - MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation, - TestClass.class); - advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext); - } - - public static class TestClass { - - public void doSomething() { - - } - - } - -} diff --git a/core/src/test/java/org/springframework/security/access/method/AuthorizationManagerMethodAfterAdviceTests.java b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptorTests.java similarity index 50% rename from core/src/test/java/org/springframework/security/access/method/AuthorizationManagerMethodAfterAdviceTests.java rename to core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptorTests.java index f2c8a96ea5..26287ed97b 100644 --- a/core/src/test/java/org/springframework/security/access/method/AuthorizationManagerMethodAfterAdviceTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptorTests.java @@ -14,55 +14,56 @@ * limitations under the License. */ -package org.springframework.security.access.method; - -import java.util.function.Supplier; +package org.springframework.security.authorization.method; import org.aopalliance.intercept.MethodInvocation; import org.junit.Test; -import org.springframework.aop.MethodMatcher; -import org.springframework.security.authentication.TestAuthentication; +import org.springframework.aop.Pointcut; 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.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; /** - * Tests for {@link AuthorizationManagerMethodAfterAdvice}. + * Tests for {@link AuthorizationManagerAfterMethodInterceptor}. * * @author Evgeniy Cheban */ -public class AuthorizationManagerMethodAfterAdviceTests { +public class AuthorizationManagerAfterMethodInterceptorTests { @Test public void instantiateWhenMethodMatcherNullThenException() { + AuthorizationManager mockAuthorizationManager = mock(AuthorizationManager.class); assertThatIllegalArgumentException() - .isThrownBy(() -> new AuthorizationManagerMethodAfterAdvice<>(null, mock(AuthorizationManager.class))) - .withMessage("methodMatcher cannot be null"); + .isThrownBy(() -> new AuthorizationManagerAfterMethodInterceptor(null, mockAuthorizationManager)) + .withMessage("pointcut cannot be null"); } @Test public void instantiateWhenAuthorizationManagerNullThenException() { assertThatIllegalArgumentException() - .isThrownBy(() -> new AuthorizationManagerMethodAfterAdvice<>(mock(MethodMatcher.class), null)) + .isThrownBy(() -> new AuthorizationManagerAfterMethodInterceptor(mock(Pointcut.class), null)) .withMessage("authorizationManager cannot be null"); } @Test - public void beforeWhenMockAuthorizationManagerThenVerifyAndReturnedObject() { - Supplier authentication = TestAuthentication::authenticatedUser; + public void beforeWhenMockAuthorizationManagerThenVerifyAndReturnedObject() throws Throwable { MethodInvocation mockMethodInvocation = mock(MethodInvocation.class); - Object returnedObject = new Object(); - AuthorizationManager mockAuthorizationManager = mock(AuthorizationManager.class); - AuthorizationManagerMethodAfterAdvice advice = new AuthorizationManagerMethodAfterAdvice<>( - mock(MethodMatcher.class), mockAuthorizationManager); - Object result = advice.after(authentication, mockMethodInvocation, returnedObject); - assertThat(result).isEqualTo(returnedObject); - verify(mockAuthorizationManager).verify(authentication, mockMethodInvocation); + MethodInvocationResult result = new MethodInvocationResult(mockMethodInvocation, new Object()); + given(mockMethodInvocation.proceed()).willReturn(result.getResult()); + AuthorizationManager mockAuthorizationManager = mock(AuthorizationManager.class); + AuthorizationManagerAfterMethodInterceptor advice = new AuthorizationManagerAfterMethodInterceptor( + Pointcut.TRUE, mockAuthorizationManager); + Object returnedObject = advice.invoke(mockMethodInvocation); + assertThat(returnedObject).isEqualTo(result.getResult()); + verify(mockAuthorizationManager).verify(eq(AuthorizationManagerAfterMethodInterceptor.AUTHENTICATION_SUPPLIER), + any(MethodInvocationResult.class)); } } diff --git a/core/src/test/java/org/springframework/security/access/method/AuthorizationManagerMethodBeforeAdviceTests.java b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptorTests.java similarity index 57% rename from core/src/test/java/org/springframework/security/access/method/AuthorizationManagerMethodBeforeAdviceTests.java rename to core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptorTests.java index 72da6d2d8f..01456bd40c 100644 --- a/core/src/test/java/org/springframework/security/access/method/AuthorizationManagerMethodBeforeAdviceTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptorTests.java @@ -14,52 +14,49 @@ * limitations under the License. */ -package org.springframework.security.access.method; - -import java.util.function.Supplier; +package org.springframework.security.authorization.method; import org.aopalliance.intercept.MethodInvocation; import org.junit.Test; -import org.springframework.aop.MethodMatcher; -import org.springframework.security.authentication.TestAuthentication; +import org.springframework.aop.Pointcut; 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}. + * Tests for {@link AuthorizationManagerBeforeMethodInterceptor}. * * @author Evgeniy Cheban */ -public class AuthorizationManagerMethodBeforeAdviceTests { +public class AuthorizationManagerBeforeMethodInterceptorTests { @Test public void instantiateWhenMethodMatcherNullThenException() { assertThatIllegalArgumentException() - .isThrownBy(() -> new AuthorizationManagerMethodBeforeAdvice<>(null, mock(AuthorizationManager.class))) - .withMessage("methodMatcher cannot be null"); + .isThrownBy( + () -> new AuthorizationManagerBeforeMethodInterceptor(null, mock(AuthorizationManager.class))) + .withMessage("pointcut cannot be null"); } @Test public void instantiateWhenAuthorizationManagerNullThenException() { assertThatIllegalArgumentException() - .isThrownBy(() -> new AuthorizationManagerMethodBeforeAdvice<>(mock(MethodMatcher.class), null)) + .isThrownBy(() -> new AuthorizationManagerBeforeMethodInterceptor(mock(Pointcut.class), null)) .withMessage("authorizationManager cannot be null"); } @Test - public void beforeWhenMockAuthorizationManagerThenVerify() { - Supplier authentication = TestAuthentication::authenticatedUser; + public void beforeWhenMockAuthorizationManagerThenVerify() throws Throwable { MethodInvocation mockMethodInvocation = mock(MethodInvocation.class); AuthorizationManager mockAuthorizationManager = mock(AuthorizationManager.class); - AuthorizationManagerMethodBeforeAdvice advice = new AuthorizationManagerMethodBeforeAdvice<>( - mock(MethodMatcher.class), mockAuthorizationManager); - advice.before(authentication, mockMethodInvocation); - verify(mockAuthorizationManager).verify(authentication, mockMethodInvocation); + AuthorizationManagerBeforeMethodInterceptor advice = new AuthorizationManagerBeforeMethodInterceptor( + Pointcut.TRUE, mockAuthorizationManager); + advice.invoke(mockMethodInvocation); + verify(mockAuthorizationManager).verify(AuthorizationManagerBeforeMethodInterceptor.AUTHENTICATION_SUPPLIER, + mockMethodInvocation); } } diff --git a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationMethodPointcutsTests.java b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationMethodPointcutsTests.java new file mode 100644 index 0000000000..b2bb8de7b3 --- /dev/null +++ b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationMethodPointcutsTests.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.authorization.method; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.Test; + +import org.springframework.aop.Pointcut; +import org.springframework.aop.support.AopUtils; +import org.springframework.security.access.prepost.PreAuthorize; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link AuthorizationMethodPointcuts} + */ +public class AuthorizationMethodPointcutsTests { + + @Test + public void forAnnotationsWhenAnnotationThenClassBasedAnnotationPointcut() { + Pointcut preAuthorize = AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class); + assertThat(AopUtils.canApply(preAuthorize, ClassController.class)).isTrue(); + assertThat(AopUtils.canApply(preAuthorize, NoController.class)).isFalse(); + } + + @Test + public void forAnnotationsWhenAnnotationThenMethodBasedAnnotationPointcut() { + Pointcut preAuthorize = AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class); + assertThat(AopUtils.canApply(preAuthorize, MethodController.class)).isTrue(); + } + + @Test + public void forAnnotationsWhenAnnotationThenClassInheritancePointcut() { + Pointcut preAuthorize = AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class); + assertThat(AopUtils.canApply(preAuthorize, InterfacedClassController.class)).isTrue(); + } + + @Test + public void forAnnotationsWhenAnnotationThenMethodInheritancePointcut() { + Pointcut preAuthorize = AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class); + assertThat(AopUtils.canApply(preAuthorize, InterfacedMethodController.class)).isTrue(); + } + + @Test + public void forAnnotationsWhenAnnotationThenAnnotationClassInheritancePointcut() { + Pointcut preAuthorize = AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class); + assertThat(AopUtils.canApply(preAuthorize, InterfacedAnnotationClassController.class)).isTrue(); + } + + @Test + public void forAnnotationsWhenAnnotationThenAnnotationMethodInheritancePointcut() { + Pointcut preAuthorize = AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class); + assertThat(AopUtils.canApply(preAuthorize, InterfacedAnnotationMethodController.class)).isTrue(); + } + + @PreAuthorize("hasAuthority('APP')") + public static class ClassController { + + String methodOne(String paramOne) { + return "value"; + } + + } + + public static class MethodController { + + @PreAuthorize("hasAuthority('APP')") + String methodOne(String paramOne) { + return "value"; + } + + } + + public static class NoController { + + String methodOne(String paramOne) { + return "value"; + } + + } + + @PreAuthorize("hasAuthority('APP')") + public interface ClassControllerInterface { + + String methodOne(String paramOne); + + } + + public static class InterfacedClassController implements ClassControllerInterface { + + public String methodOne(String paramOne) { + return "value"; + } + + } + + public interface MethodControllerInterface { + + @PreAuthorize("hasAuthority('APP')") + String methodOne(String paramOne); + + } + + public static class InterfacedMethodController implements MethodControllerInterface { + + public String methodOne(String paramOne) { + return "value"; + } + + } + + @Target({ ElementType.METHOD, ElementType.TYPE }) + @Retention(RetentionPolicy.RUNTIME) + @PreAuthorize("hasAuthority('APP')") + @interface MyAnnotation { + + } + + @MyAnnotation + public interface ClassAnnotationControllerInterface { + + String methodOne(String paramOne); + + } + + public static class InterfacedAnnotationClassController implements ClassAnnotationControllerInterface { + + public String methodOne(String paramOne) { + return "value"; + } + + } + + public interface MethodAnnotationControllerInterface { + + @MyAnnotation + String methodOne(String paramOne); + + } + + public static class InterfacedAnnotationMethodController implements MethodAnnotationControllerInterface { + + public String methodOne(String paramOne) { + return "value"; + } + + } + +} diff --git a/core/src/test/java/org/springframework/security/authorization/method/Jsr250AuthorizationManagerTests.java b/core/src/test/java/org/springframework/security/authorization/method/Jsr250AuthorizationManagerTests.java new file mode 100644 index 0000000000..778738bb4e --- /dev/null +++ b/core/src/test/java/org/springframework/security/authorization/method/Jsr250AuthorizationManagerTests.java @@ -0,0 +1,278 @@ +/* + * 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.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +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.core.annotation.AnnotationConfigurationException; +import org.springframework.security.access.intercept.method.MockMethodInvocation; +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.assertThatExceptionOfType; +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"); + Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); + AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation); + assertThat(decision).isNull(); + } + + @Test + public void checkPermitAllWhenRoleUserThenGrantedDecision() throws Exception { + MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, "permitAll"); + Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); + AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation); + assertThat(decision).isNotNull(); + assertThat(decision.isGranted()).isTrue(); + } + + @Test + public void checkDenyAllWhenRoleAdminThenDeniedDecision() throws Exception { + MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, "denyAll"); + Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); + AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedAdmin, methodInvocation); + assertThat(decision).isNotNull(); + assertThat(decision.isGranted()).isFalse(); + } + + @Test + public void checkRolesAllowedUserOrAdminWhenRoleUserThenGrantedDecision() throws Exception { + MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + "rolesAllowedUserOrAdmin"); + Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); + AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation); + assertThat(decision).isNotNull(); + assertThat(decision.isGranted()).isTrue(); + } + + @Test + public void checkRolesAllowedUserOrAdminWhenRoleAdminThenGrantedDecision() throws Exception { + MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + "rolesAllowedUserOrAdmin"); + Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); + AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedAdmin, methodInvocation); + assertThat(decision).isNotNull(); + assertThat(decision.isGranted()).isTrue(); + } + + @Test + public void checkRolesAllowedUserOrAdminWhenRoleAnonymousThenDeniedDecision() throws Exception { + Supplier authentication = () -> new TestingAuthenticationToken("user", "password", + "ROLE_ANONYMOUS"); + MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + "rolesAllowedUserOrAdmin"); + Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); + AuthorizationDecision decision = manager.check(authentication, methodInvocation); + assertThat(decision).isNotNull(); + assertThat(decision.isGranted()).isFalse(); + } + + @Test + public void checkMultipleAnnotationsWhenInvokedThenAnnotationConfigurationException() throws Exception { + Supplier authentication = () -> new TestingAuthenticationToken("user", "password", + "ROLE_ANONYMOUS"); + MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + "multipleAnnotations"); + Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); + assertThatExceptionOfType(AnnotationConfigurationException.class) + .isThrownBy(() -> manager.check(authentication, methodInvocation)); + } + + @Test + public void checkRequiresAdminWhenClassAnnotationsThenMethodAnnotationsTakePrecedence() throws Exception { + Supplier authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER"); + MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(), + ClassLevelAnnotations.class, "rolesAllowedAdmin"); + Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); + AuthorizationDecision decision = manager.check(authentication, methodInvocation); + assertThat(decision.isGranted()).isFalse(); + authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_ADMIN"); + decision = manager.check(authentication, methodInvocation); + assertThat(decision.isGranted()).isTrue(); + } + + @Test + public void checkDeniedWhenClassAnnotationsThenMethodAnnotationsTakePrecedence() throws Exception { + Supplier authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER"); + MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(), + ClassLevelAnnotations.class, "denyAll"); + Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); + AuthorizationDecision decision = manager.check(authentication, methodInvocation); + assertThat(decision.isGranted()).isFalse(); + } + + @Test + public void checkRequiresUserWhenClassAnnotationsThenApplies() throws Exception { + Supplier authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER"); + MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(), + ClassLevelAnnotations.class, "rolesAllowedUser"); + Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); + AuthorizationDecision decision = manager.check(authentication, methodInvocation); + assertThat(decision.isGranted()).isTrue(); + authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_ADMIN"); + decision = manager.check(authentication, methodInvocation); + assertThat(decision.isGranted()).isFalse(); + } + + @Test + public void checkInheritedAnnotationsWhenDuplicatedThenAnnotationConfigurationException() throws Exception { + Supplier authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER"); + MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + "inheritedAnnotations"); + Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); + assertThatExceptionOfType(AnnotationConfigurationException.class) + .isThrownBy(() -> manager.check(authentication, methodInvocation)); + } + + @Test + public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationException() throws Exception { + Supplier authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER"); + MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(), + ClassLevelAnnotations.class, "inheritedAnnotations"); + Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); + assertThatExceptionOfType(AnnotationConfigurationException.class) + .isThrownBy(() -> manager.check(authentication, methodInvocation)); + } + + public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo { + + public void doSomething() { + + } + + @DenyAll + public void denyAll() { + + } + + @PermitAll + public void permitAll() { + + } + + @RolesAllowed({ "USER", "ADMIN" }) + public void rolesAllowedUserOrAdmin() { + + } + + @RolesAllowed("USER") + @DenyAll + public void multipleAnnotations() { + + } + + public void inheritedAnnotations() { + + } + + } + + @RolesAllowed("USER") + public static class ClassLevelAnnotations implements InterfaceAnnotationsThree { + + @RolesAllowed("ADMIN") + public void rolesAllowedAdmin() { + + } + + @DenyAll + public void denyAll() { + + } + + public void rolesAllowedUser() { + + } + + @Override + @PermitAll + public void inheritedAnnotations() { + + } + + } + + public interface InterfaceAnnotationsOne { + + @RolesAllowed("ADMIN") + void inheritedAnnotations(); + + } + + public interface InterfaceAnnotationsTwo { + + @MyRolesAllowed + void inheritedAnnotations(); + + } + + public interface InterfaceAnnotationsThree { + + @DenyAll + void inheritedAnnotations(); + + } + + @Retention(RetentionPolicy.RUNTIME) + @RolesAllowed("USER") + public @interface MyRolesAllowed { + + } + +} diff --git a/core/src/test/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManagerTests.java b/core/src/test/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManagerTests.java index 62da757d15..e6a068a4cf 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManagerTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManagerTests.java @@ -16,21 +16,27 @@ package org.springframework.security.authorization.method; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.function.Supplier; import org.junit.Test; +import org.springframework.core.annotation.AnnotationConfigurationException; 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.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.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** @@ -57,38 +63,32 @@ public class PostAuthorizeAuthorizationManagerTests { @Test public void checkDoSomethingWhenNoPostAuthorizeAnnotationThenNullDecision() throws Exception { - MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + MockMethodInvocation methodInvocation = 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); + MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null); + AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, result); assertThat(decision).isNull(); } @Test public void checkDoSomethingStringWhenArgIsGrantThenGrantedDecision() throws Exception { - MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + MockMethodInvocation methodInvocation = 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); + MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null); + AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, result); assertThat(decision).isNotNull(); assertThat(decision.isGranted()).isTrue(); } @Test public void checkDoSomethingStringWhenArgIsNotGrantThenDeniedDecision() throws Exception { - MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, "doSomethingString", new Class[] { String.class }, new Object[] { "deny" }); - MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation, - TestClass.class); + MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null); PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager(); - AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, - methodAuthorizationContext); + AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, result); assertThat(decision).isNotNull(); assertThat(decision.isGranted()).isFalse(); } @@ -96,14 +96,11 @@ public class PostAuthorizeAuthorizationManagerTests { @Test public void checkDoSomethingListWhenReturnObjectContainsGrantThenGrantedDecision() throws Exception { List list = Arrays.asList("grant", "deny"); - MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, "doSomethingList", new Class[] { List.class }, new Object[] { list }); - MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation, - TestClass.class); - methodAuthorizationContext.setReturnObject(list); + MethodInvocationResult result = new MethodInvocationResult(methodInvocation, list); PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager(); - AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, - methodAuthorizationContext); + AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, result); assertThat(decision).isNotNull(); assertThat(decision.isGranted()).isTrue(); } @@ -111,19 +108,66 @@ public class PostAuthorizeAuthorizationManagerTests { @Test public void checkDoSomethingListWhenReturnObjectNotContainsGrantThenDeniedDecision() throws Exception { List list = Collections.singletonList("deny"); - MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, "doSomethingList", new Class[] { List.class }, new Object[] { list }); - MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation, - TestClass.class); - methodAuthorizationContext.setReturnObject(list); + MethodInvocationResult result = new MethodInvocationResult(methodInvocation, list); PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager(); - AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, - methodAuthorizationContext); + AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, result); assertThat(decision).isNotNull(); assertThat(decision.isGranted()).isFalse(); } - public static class TestClass { + @Test + public void checkRequiresAdminWhenClassAnnotationsThenMethodAnnotationsTakePrecedence() throws Exception { + Supplier authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER"); + MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(), + ClassLevelAnnotations.class, "securedAdmin"); + MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null); + PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager(); + AuthorizationDecision decision = manager.check(authentication, result); + assertThat(decision.isGranted()).isFalse(); + authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_ADMIN"); + decision = manager.check(authentication, result); + assertThat(decision.isGranted()).isTrue(); + } + + @Test + public void checkRequiresUserWhenClassAnnotationsThenApplies() throws Exception { + Supplier authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER"); + MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(), + ClassLevelAnnotations.class, "securedUser"); + MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null); + PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager(); + AuthorizationDecision decision = manager.check(authentication, result); + assertThat(decision.isGranted()).isTrue(); + authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_ADMIN"); + decision = manager.check(authentication, result); + assertThat(decision.isGranted()).isFalse(); + } + + @Test + public void checkInheritedAnnotationsWhenDuplicatedThenAnnotationConfigurationException() throws Exception { + Supplier authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER"); + MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + "inheritedAnnotations"); + MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null); + PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager(); + assertThatExceptionOfType(AnnotationConfigurationException.class) + .isThrownBy(() -> manager.check(authentication, result)); + } + + @Test + public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationException() throws Exception { + Supplier authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER"); + MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(), + ClassLevelAnnotations.class, "inheritedAnnotations"); + MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null); + PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager(); + assertThatExceptionOfType(AnnotationConfigurationException.class) + .isThrownBy(() -> manager.check(authentication, result)); + } + + public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo { public void doSomething() { @@ -139,6 +183,58 @@ public class PostAuthorizeAuthorizationManagerTests { return list; } + @Override + public void inheritedAnnotations() { + + } + + } + + @PostAuthorize("hasRole('USER')") + public static class ClassLevelAnnotations implements InterfaceAnnotationsThree { + + @PostAuthorize("hasRole('ADMIN')") + public void securedAdmin() { + + } + + public void securedUser() { + + } + + @Override + @PostAuthorize("hasRole('ADMIN')") + public void inheritedAnnotations() { + + } + + } + + public interface InterfaceAnnotationsOne { + + @PostAuthorize("hasRole('ADMIN')") + void inheritedAnnotations(); + + } + + public interface InterfaceAnnotationsTwo { + + @PostAuthorize("hasRole('USER')") + void inheritedAnnotations(); + + } + + public interface InterfaceAnnotationsThree { + + @MyPostAuthorize + void inheritedAnnotations(); + + } + + @Retention(RetentionPolicy.RUNTIME) + @PostAuthorize("hasRole('USER')") + public @interface MyPostAuthorize { + } } diff --git a/core/src/test/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodAfterAdviceTests.java b/core/src/test/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodAfterAdviceTests.java deleted file mode 100644 index 32e5784fe3..0000000000 --- a/core/src/test/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodAfterAdviceTests.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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; - } - - } - -} diff --git a/core/src/test/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptorTests.java b/core/src/test/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptorTests.java new file mode 100644 index 0000000000..8a5c9aa583 --- /dev/null +++ b/core/src/test/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptorTests.java @@ -0,0 +1,186 @@ +/* + * 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.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.assertj.core.api.InstanceOfAssertFactories; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.aop.MethodMatcher; +import org.springframework.core.annotation.AnnotationConfigurationException; +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.prepost.PostFilter; +import org.springframework.security.authentication.TestAuthentication; +import org.springframework.security.core.context.SecurityContextHolder; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link PostFilterAuthorizationMethodInterceptor}. + * + * @author Evgeniy Cheban + */ +public class PostFilterAuthorizationMethodInterceptorTests { + + @Before + public void setUp() { + SecurityContextHolder.getContext().setAuthentication(TestAuthentication.authenticatedUser()); + } + + @After + public void tearDown() { + SecurityContextHolder.clearContext(); + } + + @Test + public void setExpressionHandlerWhenNotNullThenSetsExpressionHandler() { + MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); + PostFilterAuthorizationMethodInterceptor advice = new PostFilterAuthorizationMethodInterceptor(); + advice.setExpressionHandler(expressionHandler); + assertThat(advice).extracting("expressionHandler").isEqualTo(expressionHandler); + } + + @Test + public void setExpressionHandlerWhenNullThenException() { + PostFilterAuthorizationMethodInterceptor advice = new PostFilterAuthorizationMethodInterceptor(); + assertThatIllegalArgumentException().isThrownBy(() -> advice.setExpressionHandler(null)) + .withMessage("expressionHandler cannot be null"); + } + + @Test + public void methodMatcherWhenMethodHasNotPostFilterAnnotationThenNotMatches() throws Exception { + PostFilterAuthorizationMethodInterceptor advice = new PostFilterAuthorizationMethodInterceptor(); + MethodMatcher methodMatcher = advice.getPointcut().getMethodMatcher(); + assertThat(methodMatcher.matches(NoPostFilterClass.class.getMethod("doSomething"), NoPostFilterClass.class)) + .isFalse(); + } + + @Test + public void methodMatcherWhenMethodHasPostFilterAnnotationThenMatches() throws Exception { + PostFilterAuthorizationMethodInterceptor advice = new PostFilterAuthorizationMethodInterceptor(); + MethodMatcher methodMatcher = advice.getPointcut().getMethodMatcher(); + assertThat( + methodMatcher.matches(TestClass.class.getMethod("doSomethingArray", String[].class), TestClass.class)) + .isTrue(); + } + + @Test + public void afterWhenArrayNotNullThenFilteredArray() throws Throwable { + String[] array = { "john", "bob" }; + MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + "doSomethingArrayClassLevel", new Class[] { String[].class }, new Object[] { array }) { + @Override + public Object proceed() { + return array; + } + }; + PostFilterAuthorizationMethodInterceptor advice = new PostFilterAuthorizationMethodInterceptor(); + Object result = advice.invoke(methodInvocation); + assertThat(result).asInstanceOf(InstanceOfAssertFactories.array(String[].class)).containsOnly("john"); + } + + @Test + public void checkInheritedAnnotationsWhenDuplicatedThenAnnotationConfigurationException() throws Exception { + MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + "inheritedAnnotations"); + PostFilterAuthorizationMethodInterceptor advice = new PostFilterAuthorizationMethodInterceptor(); + assertThatExceptionOfType(AnnotationConfigurationException.class) + .isThrownBy(() -> advice.invoke(methodInvocation)); + } + + @Test + public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationException() throws Exception { + MockMethodInvocation methodInvocation = new MockMethodInvocation(new ConflictingAnnotations(), + ConflictingAnnotations.class, "inheritedAnnotations"); + PostFilterAuthorizationMethodInterceptor advice = new PostFilterAuthorizationMethodInterceptor(); + assertThatExceptionOfType(AnnotationConfigurationException.class) + .isThrownBy(() -> advice.invoke(methodInvocation)); + } + + @PostFilter("filterObject == 'john'") + public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo { + + @PostFilter("filterObject == 'john'") + public String[] doSomethingArray(String[] array) { + return array; + } + + public String[] doSomethingArrayClassLevel(String[] array) { + return array; + } + + @Override + public void inheritedAnnotations() { + + } + + } + + public static class NoPostFilterClass { + + public void doSomething() { + + } + + } + + public static class ConflictingAnnotations implements InterfaceAnnotationsThree { + + @Override + @PostFilter("filterObject == 'jack'") + public void inheritedAnnotations() { + + } + + } + + public interface InterfaceAnnotationsOne { + + @PostFilter("filterObject == 'jim'") + void inheritedAnnotations(); + + } + + public interface InterfaceAnnotationsTwo { + + @PostFilter("filterObject == 'jane'") + void inheritedAnnotations(); + + } + + public interface InterfaceAnnotationsThree { + + @MyPostFilter + void inheritedAnnotations(); + + } + + @Retention(RetentionPolicy.RUNTIME) + @PostFilter("filterObject == 'john'") + public @interface MyPostFilter { + + } + +} diff --git a/core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManagerTests.java b/core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManagerTests.java index ebabf1c5e3..0247617cfb 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManagerTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManagerTests.java @@ -16,17 +16,24 @@ package org.springframework.security.authorization.method; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.function.Supplier; + import org.junit.Test; +import org.springframework.core.annotation.AnnotationConfigurationException; 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.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.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** @@ -53,43 +60,80 @@ public class PreAuthorizeAuthorizationManagerTests { @Test public void checkDoSomethingWhenNoPostAuthorizeAnnotationThenNullDecision() throws Exception { - MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + MockMethodInvocation methodInvocation = 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); + AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation); assertThat(decision).isNull(); } @Test public void checkDoSomethingStringWhenArgIsGrantThenGrantedDecision() throws Exception { - MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + MockMethodInvocation methodInvocation = 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); + AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation); assertThat(decision).isNotNull(); assertThat(decision.isGranted()).isTrue(); } @Test public void checkDoSomethingStringWhenArgIsNotGrantThenDeniedDecision() throws Exception { - MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + MockMethodInvocation methodInvocation = 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); + AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation); assertThat(decision).isNotNull(); assertThat(decision.isGranted()).isFalse(); } - public static class TestClass { + @Test + public void checkRequiresAdminWhenClassAnnotationsThenMethodAnnotationsTakePrecedence() throws Exception { + Supplier authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER"); + MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(), + ClassLevelAnnotations.class, "securedAdmin"); + PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager(); + AuthorizationDecision decision = manager.check(authentication, methodInvocation); + assertThat(decision.isGranted()).isFalse(); + authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_ADMIN"); + decision = manager.check(authentication, methodInvocation); + assertThat(decision.isGranted()).isTrue(); + } + + @Test + public void checkRequiresUserWhenClassAnnotationsThenApplies() throws Exception { + Supplier authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER"); + MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(), + ClassLevelAnnotations.class, "securedUser"); + PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager(); + AuthorizationDecision decision = manager.check(authentication, methodInvocation); + assertThat(decision.isGranted()).isTrue(); + authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_ADMIN"); + decision = manager.check(authentication, methodInvocation); + assertThat(decision.isGranted()).isFalse(); + } + + @Test + public void checkInheritedAnnotationsWhenDuplicatedThenAnnotationConfigurationException() throws Exception { + Supplier authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER"); + MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + "inheritedAnnotations"); + PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager(); + assertThatExceptionOfType(AnnotationConfigurationException.class) + .isThrownBy(() -> manager.check(authentication, methodInvocation)); + } + + @Test + public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationException() throws Exception { + Supplier authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER"); + MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(), + ClassLevelAnnotations.class, "inheritedAnnotations"); + PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager(); + assertThatExceptionOfType(AnnotationConfigurationException.class) + .isThrownBy(() -> manager.check(authentication, methodInvocation)); + } + + public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo { public void doSomething() { @@ -100,6 +144,58 @@ public class PreAuthorizeAuthorizationManagerTests { return s; } + @Override + public void inheritedAnnotations() { + + } + + } + + @PreAuthorize("hasRole('USER')") + public static class ClassLevelAnnotations implements InterfaceAnnotationsThree { + + @PreAuthorize("hasRole('ADMIN')") + public void securedAdmin() { + + } + + public void securedUser() { + + } + + @Override + @PreAuthorize("hasRole('ADMIN')") + public void inheritedAnnotations() { + + } + + } + + public interface InterfaceAnnotationsOne { + + @PreAuthorize("hasRole('ADMIN')") + void inheritedAnnotations(); + + } + + public interface InterfaceAnnotationsTwo { + + @PreAuthorize("hasRole('USER')") + void inheritedAnnotations(); + + } + + public interface InterfaceAnnotationsThree { + + @MyPreAuthorize + void inheritedAnnotations(); + + } + + @Retention(RetentionPolicy.RUNTIME) + @PreAuthorize("hasRole('USER')") + public @interface MyPreAuthorize { + } } diff --git a/core/src/test/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodBeforeAdviceTests.java b/core/src/test/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodBeforeAdviceTests.java deleted file mode 100644 index c38b4b165a..0000000000 --- a/core/src/test/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodBeforeAdviceTests.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * 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 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 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 doSomethingListFilterTargetNotMatch(List list) { - return list; - } - - @PreFilter(value = "filterObject == 'john'", filterTarget = "list") - public List doSomethingListFilterTargetMatch(List list) { - return list; - } - - @PreFilter("filterObject == 'john'") - public List doSomethingListFilterTargetNotProvided(List list) { - return list; - } - - @PreFilter("filterObject == 'john'") - public String[] doSomethingArrayFilterTargetNotProvided(String[] array) { - return array; - } - - @PreFilter("filterObject == 'john'") - public List doSomethingTwoArgsFilterTargetNotProvided(String s, List list) { - return list; - } - - } - -} diff --git a/core/src/test/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptorTests.java b/core/src/test/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptorTests.java new file mode 100644 index 0000000000..78432a5e5d --- /dev/null +++ b/core/src/test/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptorTests.java @@ -0,0 +1,260 @@ +/* + * 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.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.aop.MethodMatcher; +import org.springframework.core.annotation.AnnotationConfigurationException; +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.prepost.PreFilter; +import org.springframework.security.authentication.TestAuthentication; +import org.springframework.security.core.context.SecurityContextHolder; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; + +/** + * Tests for {@link PreFilterAuthorizationMethodInterceptor}. + * + * @author Evgeniy Cheban + */ +public class PreFilterAuthorizationMethodInterceptorTests { + + @Before + public void setUp() { + SecurityContextHolder.getContext().setAuthentication(TestAuthentication.authenticatedUser()); + } + + @After + public void tearDown() { + SecurityContextHolder.clearContext(); + } + + @Test + public void setExpressionHandlerWhenNotNullThenSetsExpressionHandler() { + MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); + PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor(); + advice.setExpressionHandler(expressionHandler); + assertThat(advice).extracting("expressionHandler").isEqualTo(expressionHandler); + } + + @Test + public void setExpressionHandlerWhenNullThenException() { + PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor(); + assertThatIllegalArgumentException().isThrownBy(() -> advice.setExpressionHandler(null)) + .withMessage("expressionHandler cannot be null"); + } + + @Test + public void methodMatcherWhenMethodHasNotPreFilterAnnotationThenNotMatches() throws Exception { + PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor(); + MethodMatcher methodMatcher = advice.getPointcut().getMethodMatcher(); + assertThat(methodMatcher.matches(NoPreFilterClass.class.getMethod("doSomething"), NoPreFilterClass.class)) + .isFalse(); + } + + @Test + public void methodMatcherWhenMethodHasPreFilterAnnotationThenMatches() throws Exception { + PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor(); + MethodMatcher methodMatcher = advice.getPointcut().getMethodMatcher(); + assertThat(methodMatcher.matches(TestClass.class.getMethod("doSomethingListFilterTargetMatch", List.class), + TestClass.class)).isTrue(); + } + + @Test + public void findFilterTargetWhenNameProvidedAndNotMatchThenException() throws Exception { + MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + "doSomethingListFilterTargetNotMatch", new Class[] { List.class }, new Object[] { new ArrayList<>() }); + PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor(); + assertThatIllegalArgumentException().isThrownBy(() -> advice.invoke(methodInvocation)).withMessage( + "Filter target was null, or no argument with name 'filterTargetNotMatch' found in method."); + } + + @Test + public void findFilterTargetWhenNameProvidedAndMatchAndNullThenException() throws Exception { + MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + "doSomethingListFilterTargetMatch", new Class[] { List.class }, new Object[] { null }); + PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor(); + assertThatIllegalArgumentException().isThrownBy(() -> advice.invoke(methodInvocation)) + .withMessage("Filter target was null, or no argument with name 'list' found in method."); + } + + @Test + public void findFilterTargetWhenNameProvidedAndMatchAndNotNullThenFiltersList() throws Throwable { + List list = new ArrayList<>(); + list.add("john"); + list.add("bob"); + MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + "doSomethingListFilterTargetMatch", new Class[] { List.class }, new Object[] { list }); + PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor(); + advice.invoke(methodInvocation); + assertThat(list).hasSize(1); + assertThat(list.get(0)).isEqualTo("john"); + } + + @Test + public void findFilterTargetWhenNameNotProvidedAndSingleArgListNullThenException() throws Exception { + MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + "doSomethingListFilterTargetNotProvided", new Class[] { List.class }, new Object[] { null }); + PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor(); + assertThatIllegalArgumentException().isThrownBy(() -> advice.invoke(methodInvocation)) + .withMessage("Filter target was null. Make sure you passing the correct value in the method argument."); + } + + @Test + public void findFilterTargetWhenNameNotProvidedAndSingleArgListThenFiltersList() throws Throwable { + List list = new ArrayList<>(); + list.add("john"); + list.add("bob"); + MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + "doSomethingListFilterTargetNotProvided", new Class[] { List.class }, new Object[] { list }); + PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor(); + advice.invoke(methodInvocation); + assertThat(list).hasSize(1); + assertThat(list.get(0)).isEqualTo("john"); + } + + @Test + public void findFilterTargetWhenNameNotProvidedAndSingleArgArrayThenException() throws Exception { + MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + "doSomethingArrayFilterTargetNotProvided", new Class[] { String[].class }, + new Object[] { new String[] {} }); + PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor(); + assertThatIllegalStateException().isThrownBy(() -> advice.invoke(methodInvocation)).withMessage( + "Pre-filtering on array types is not supported. Using a Collection will solve this problem."); + } + + @Test + public void findFilterTargetWhenNameNotProvidedAndNotSingleArgThenException() throws Exception { + MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + "doSomethingTwoArgsFilterTargetNotProvided", new Class[] { String.class, List.class }, + new Object[] { "", new ArrayList<>() }); + PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor(); + assertThatIllegalStateException().isThrownBy(() -> advice.invoke(methodInvocation)) + .withMessage("Unable to determine the method argument for filtering. Specify the filter target."); + } + + @Test + public void checkInheritedAnnotationsWhenDuplicatedThenAnnotationConfigurationException() throws Exception { + MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + "inheritedAnnotations"); + PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor(); + assertThatExceptionOfType(AnnotationConfigurationException.class) + .isThrownBy(() -> advice.invoke(methodInvocation)); + } + + @Test + public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationException() throws Exception { + MockMethodInvocation methodInvocation = new MockMethodInvocation(new ConflictingAnnotations(), + ConflictingAnnotations.class, "inheritedAnnotations"); + PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor(); + assertThatExceptionOfType(AnnotationConfigurationException.class) + .isThrownBy(() -> advice.invoke(methodInvocation)); + } + + @PreFilter("filterObject == 'john'") + public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo { + + @PreFilter(value = "filterObject == 'john'", filterTarget = "filterTargetNotMatch") + public List doSomethingListFilterTargetNotMatch(List list) { + return list; + } + + @PreFilter(value = "filterObject == 'john'", filterTarget = "list") + public List doSomethingListFilterTargetMatch(List list) { + return list; + } + + @PreFilter("filterObject == 'john'") + public List doSomethingListFilterTargetNotProvided(List list) { + return list; + } + + @PreFilter("filterObject == 'john'") + public String[] doSomethingArrayFilterTargetNotProvided(String[] array) { + return array; + } + + public List doSomethingTwoArgsFilterTargetNotProvided(String s, List list) { + return list; + } + + @Override + public void inheritedAnnotations() { + + } + + } + + public static class NoPreFilterClass { + + public void doSomething() { + + } + + } + + public static class ConflictingAnnotations implements InterfaceAnnotationsThree { + + @Override + @PreFilter("filterObject == 'jack'") + public void inheritedAnnotations() { + + } + + } + + public interface InterfaceAnnotationsOne { + + @PreFilter("filterObject == 'jim'") + void inheritedAnnotations(); + + } + + public interface InterfaceAnnotationsTwo { + + @PreFilter("filterObject == 'jane'") + void inheritedAnnotations(); + + } + + public interface InterfaceAnnotationsThree { + + @MyPreFilter + void inheritedAnnotations(); + + } + + @Retention(RetentionPolicy.RUNTIME) + @PreFilter("filterObject == 'john'") + public @interface MyPreFilter { + + } + +} diff --git a/core/src/test/java/org/springframework/security/authorization/method/SecuredAuthorizationManagerTests.java b/core/src/test/java/org/springframework/security/authorization/method/SecuredAuthorizationManagerTests.java new file mode 100644 index 0000000000..3f1e507731 --- /dev/null +++ b/core/src/test/java/org/springframework/security/authorization/method/SecuredAuthorizationManagerTests.java @@ -0,0 +1,195 @@ +/* + * 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.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.function.Supplier; + +import org.junit.Test; + +import org.springframework.core.annotation.AnnotationConfigurationException; +import org.springframework.security.access.annotation.Secured; +import org.springframework.security.access.intercept.method.MockMethodInvocation; +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.assertThatExceptionOfType; + +/** + * 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"); + SecuredAuthorizationManager manager = new SecuredAuthorizationManager(); + AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation); + assertThat(decision).isNull(); + } + + @Test + public void checkSecuredUserOrAdminWhenRoleUserThenGrantedDecision() throws Exception { + MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + "securedUserOrAdmin"); + SecuredAuthorizationManager manager = new SecuredAuthorizationManager(); + AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation); + assertThat(decision).isNotNull(); + assertThat(decision.isGranted()).isTrue(); + } + + @Test + public void checkSecuredUserOrAdminWhenRoleAdminThenGrantedDecision() throws Exception { + MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + "securedUserOrAdmin"); + SecuredAuthorizationManager manager = new SecuredAuthorizationManager(); + AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedAdmin, methodInvocation); + assertThat(decision).isNotNull(); + assertThat(decision.isGranted()).isTrue(); + } + + @Test + public void checkSecuredUserOrAdminWhenRoleAnonymousThenDeniedDecision() throws Exception { + Supplier authentication = () -> new TestingAuthenticationToken("user", "password", + "ROLE_ANONYMOUS"); + MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + "securedUserOrAdmin"); + SecuredAuthorizationManager manager = new SecuredAuthorizationManager(); + AuthorizationDecision decision = manager.check(authentication, methodInvocation); + assertThat(decision).isNotNull(); + assertThat(decision.isGranted()).isFalse(); + } + + @Test + public void checkRequiresAdminWhenClassAnnotationsThenMethodAnnotationsTakePrecedence() throws Exception { + Supplier authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER"); + MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(), + ClassLevelAnnotations.class, "securedAdmin"); + SecuredAuthorizationManager manager = new SecuredAuthorizationManager(); + AuthorizationDecision decision = manager.check(authentication, methodInvocation); + assertThat(decision.isGranted()).isFalse(); + authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_ADMIN"); + decision = manager.check(authentication, methodInvocation); + assertThat(decision.isGranted()).isTrue(); + } + + @Test + public void checkRequiresUserWhenClassAnnotationsThenApplies() throws Exception { + Supplier authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER"); + MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(), + ClassLevelAnnotations.class, "securedUser"); + SecuredAuthorizationManager manager = new SecuredAuthorizationManager(); + AuthorizationDecision decision = manager.check(authentication, methodInvocation); + assertThat(decision.isGranted()).isTrue(); + authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_ADMIN"); + decision = manager.check(authentication, methodInvocation); + assertThat(decision.isGranted()).isFalse(); + } + + @Test + public void checkInheritedAnnotationsWhenDuplicatedThenAnnotationConfigurationException() throws Exception { + Supplier authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER"); + MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + "inheritedAnnotations"); + SecuredAuthorizationManager manager = new SecuredAuthorizationManager(); + assertThatExceptionOfType(AnnotationConfigurationException.class) + .isThrownBy(() -> manager.check(authentication, methodInvocation)); + } + + @Test + public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationException() throws Exception { + Supplier authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER"); + MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(), + ClassLevelAnnotations.class, "inheritedAnnotations"); + SecuredAuthorizationManager manager = new SecuredAuthorizationManager(); + assertThatExceptionOfType(AnnotationConfigurationException.class) + .isThrownBy(() -> manager.check(authentication, methodInvocation)); + } + + public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo { + + public void doSomething() { + + } + + @Secured({ "ROLE_USER", "ROLE_ADMIN" }) + public void securedUserOrAdmin() { + + } + + @Override + public void inheritedAnnotations() { + + } + + } + + @Secured("ROLE_USER") + public static class ClassLevelAnnotations implements InterfaceAnnotationsThree { + + @Secured("ROLE_ADMIN") + public void securedAdmin() { + + } + + public void securedUser() { + + } + + @Override + @Secured("ROLE_ADMIN") + public void inheritedAnnotations() { + + } + + } + + public interface InterfaceAnnotationsOne { + + @Secured("ROLE_ADMIN") + void inheritedAnnotations(); + + } + + public interface InterfaceAnnotationsTwo { + + @Secured("ROLE_USER") + void inheritedAnnotations(); + + } + + public interface InterfaceAnnotationsThree { + + @MySecured + void inheritedAnnotations(); + + } + + @Retention(RetentionPolicy.RUNTIME) + @Secured("ROLE_USER") + public @interface MySecured { + + } + +} diff --git a/docs/manual/src/docs/asciidoc/_includes/about/whats-new.adoc b/docs/manual/src/docs/asciidoc/_includes/about/whats-new.adoc index 091b99142f..05d0ba0497 100644 --- a/docs/manual/src/docs/asciidoc/_includes/about/whats-new.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/about/whats-new.adoc @@ -1,51 +1,11 @@ [[new]] -== What's New in Spring Security 5.5 +== What's New in Spring Security 5.6 -Spring Security 5.5 provides a number of new features. +Spring Security 5.6 provides a number of new features. Below are the highlights of the release. [[whats-new-servlet]] === Servlet -* OAuth 2.0 Client - -** Added support for https://github.com/spring-projects/spring-security/pull/9520[Jwt Client Authentication] `private_key_jwt` and `client_secret_jwt` -** Added https://github.com/spring-projects/spring-security/pull/9535[Jwt Bearer Authorization Grant] support -** Added https://github.com/spring-projects/spring-security/pull/8765[R2DBC implementation] of `ReactiveOAuth2AuthorizedClientService` - -* OAuth 2.0 Resource Server - -** Enhanced JWT decoders https://github.com/spring-projects/spring-security/issues/7160[derivation of signature algorithms] -** Improved https://github.com/spring-projects/spring-security/issues/9100[content negotation] -** Improved https://github.com/spring-projects/spring-security/issues/9186[multi-tenancy support] - -* SAML 2.0 Service Provider - -** Added https://github.com/spring-projects/spring-security/issues/9095[OpenSAML 4 support] -** Enhanced SAML 2.0 https://github.com/spring-projects/spring-security/issues/9131[Assertion] https://github.com/spring-projects/spring-security/issues/9044[decryption] -** Added https://github.com/spring-projects/spring-security/issues/9028[file-based configuration] for asserting party metadata -** Improved https://github.com/spring-projects/spring-security/issues/9486[`RelyingPartyRegistration` resolution] support -** Enhanced relying party metadata https://github.com/spring-projects/spring-security/issues/9317[support] -** Added support for https://github.com/spring-projects/spring-security/issues/9177[AP-specified signing methods] - * Configuration -** Introduced https://github.com/spring-projects/spring-security/issues/9205[DispatcherType request matcher] -** Introduced https://github.com/spring-projects/spring-security/issues/8900[`AuthorizationManager`] for filter security - -* Kotlin DSL - -** Added https://github.com/spring-projects/spring-security/issues/9319[rememberMe support] - -[[whats-new-webflux]] -=== WebFlux - -** Added https://github.com/spring-projects/spring-security/issues/8143[Kotlin coroutine support] for `EnableReactiveMethodSecurity` - -[[whats-new-build]] -=== Build - -The build has been modernized in a number of different ways. -Here are a couple of highlights: - -* All sample applications have been moved to https://github.com/spring-projects/spring-security-samples[a separate project] -* The full build now https://github.com/spring-projects/spring-security/issues/9419[works with JDK 11] +** Introduced https://github.com/spring-projects/spring-security/pull/9630[`AuthorizationManager`] for method security diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/namespace.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/namespace.adoc index f65ec3349d..3d5b0dca6b 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/namespace.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/namespace.adoc @@ -759,6 +759,7 @@ A default implementation (with no ACL support) will be used if not supplied. * <> * <> +* <> * <> @@ -2374,9 +2375,41 @@ If omitted, the namespace will generate a random value, preventing its accidenta Cannot be empty. -[[nsa-method-security]] + === Method Security +[[nsa-method-security]] +==== +This element is the primary means of adding support for securing methods on Spring Security beans. +Methods can be secured by the use of annotations (defined at the interface or class level) or by defining a set of pointcuts. + +[[nsa-method-security-attributes]] +===== attributes + +[[nsa-method-security-pre-post-enabled]] +* **pre-post-enabled** +Enables Spring Security's pre and post invocation annotations (@PreFilter, @PreAuthorize, @PostFilter, @PostAuthorize) for this application context. +Defaults to "true". + +[[nsa-method-security-secured-enabled]] +* **secured-enabled** +Enables Spring Security's @Secured annotation for this application context. +Defaults to "false". + +[[nsa-method-security-jsr250-enabled]] +* **jsr250-enabled** +Enables JSR-250 authorization annotations (@RolesAllowed, @PermitAll, @DenyAll) for this application context. +Defaults to "false". + +[[nsa-method-security-proxy-target-class]] +* **proxy-target-class** +If true, class based proxying will be used instead of interface based proxying. +Defaults to "false". + +[[nsa-method-security-children]] +===== Child Elements of + +* <> [[nsa-global-method-security]] ==== diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/authorization/method-security.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/authorization/method-security.adoc index 573412f6bf..a05b62dfc7 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/authorization/method-security.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/authorization/method-security.adoc @@ -6,6 +6,598 @@ It provides support for JSR-250 annotation security as well as the framework's o From 3.0 you can also make use of new <>. You can apply security to a single bean, using the `intercept-methods` element to decorate the bean declaration, or you can secure multiple beans across the entire service layer using the AspectJ style pointcuts. +=== EnableMethodSecurity + +In Spring Security 5.6, we can enable annotation-based security using the `@EnableMethodSecurity` annotation on any `@Configuration` instance. + +This improves upon `@EnableGlobalMethodSecurity` in a number of ways. `@EnableMethodSecurity`: + +1. Uses the simplified `AuthorizationManager` API instead of metadata sources, config attributes, decision managers, and voters. +This simplifies reuse and customization. +2. Favors direct bean-based configuration, instead of requiring extending `GlobalMethodSecurityConfiguration` to customize beans +3. Is built using native Spring AOP, removing abstractions and allowing you to use Spring AOP building blocks to customize +4. Checks for conflicting annotations to ensure an unambiguous security configuration +5. Complies with JSR-250 +6. Enables `@PreAuthorize`, `@PostAuthorize`, `@PreFilter`, and `@PostFilter` by default + +[NOTE] +==== +For earlier versions, please read about similar support with <>. +==== + +For example, the following would enable Spring Security's `@PreAuthorize` annotation: + +.Method Security Configuration +==== +.Java +[source,java,role="primary"] +---- +@EnableMethodSecurity +public class MethodSecurityConfig { + // ... +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +@EnableMethodSecurity +class MethodSecurityConfig { + // ... +} +---- + +.Xml +[source,xml,role="secondary"] +---- + +---- +==== + +Adding an annotation to a method (on a class or interface) would then limit the access to that method accordingly. +Spring Security's native annotation support defines a set of attributes for the method. +These will be passed to the `DefaultAuthorizationMethodInterceptorChain` for it to make the actual decision: + +.Method Security Annotation Usage +==== +.Java +[source,java,role="primary"] +---- +public interface BankService { + @PreAuthorize("hasRole('USER')") + Account readAccount(Long id); + + @PreAuthorize("hasRole('USER')") + List findAccounts(); + + @PreAuthorize("hasRole('TELLER')") + Account post(Account account, Double amount); +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +interface BankService { + @PreAuthorize("hasRole('USER')") + fun readAccount(id : Long) : Account + + @PreAuthorize("hasRole('USER')") + fun findAccounts() : List + + @PreAuthorize("hasRole('TELLER')") + fun post(account : Account, amount : Double) : Account +} +---- +==== + +You can enable support for Spring Security's `@Secured` annotation using: + +.@Secured Configuration +==== +.Java +[source,java,role="primary"] +---- +@EnableMethodSecurity(securedEnabled = true) +public class MethodSecurityConfig { + // ... +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +@EnableMethodSecurity(securedEnabled = true) +class MethodSecurityConfig { + // ... +} +---- + +.Xml +[source,xml,role="secondary"] +---- + +---- +==== + +or JSR-250 using: + +.JSR-250 Configuration +==== +.Java +[source,java,role="primary"] +---- +@EnableMethodSecurity(jsr250Enabled = true) +public class MethodSecurityConfig { + // ... +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +@EnableMethodSecurity(jsr250Enabled = true) +class MethodSecurityConfig { + // ... +} +---- + +.Xml +[source,xml,role="secondary"] +---- + +---- +==== + +==== Customizing Authorization + +Spring Security's `@PreAuthorize`, `@PostAuthorize`, `@PreFilter`, and `@PostFilter` ship with rich expression-based support. + +[[jc-method-security-custom-expression-handler]] +If you need to customize the way that expressions are handled, you can expose a custom `MethodSecurityExpressionHandler`, like so: + +.Custom MethodSecurityExpressionHandler +==== +.Java +[source,java,role="primary"] +---- +@Bean +static MethodSecurityExpressionHandler methodSecurityExpressionHandler() { + DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler(); + handler.setTrustResolver(myCustomTrustResolver); + return handler; +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +companion object { + @Bean + fun methodSecurityExpressionHandler() : MethodSecurityExpressionHandler { + val handler = DefaultMethodSecurityExpressionHandler(); + handler.setTrustResolver(myCustomTrustResolver); + return handler; + } +} +---- + +.Xml +[source,xml,role="secondary"] +---- + + + + + + + +---- +==== + +[TIP] +==== +We expose `MethodSecurityExpressionHandler` using a `static` method to ensure that Spring publishes it before it initializes Spring Security's method security `@Configuration` classes +==== + +Also, for role-based authorization, Spring Security adds a default `ROLE_` prefix, which is uses when evaluating expressions like `hasRole`. + +[[jc-method-security-custom-granted-authority-defaults]] +You can configure the authorization rules to use a different prefix by exposing a `GrantedAuthorityDefaults` bean, like so: + +.Custom MethodSecurityExpressionHandler +==== +.Java +[source,java,role="primary"] +---- +@Bean +static GrantedAuthorityDefaults grantedAuthorityDefaults() { + return new GrantedAuthorityDefaults("MYPREFIX_"); +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +companion object { + @Bean + fun grantedAuthorityDefaults() : GrantedAuthorityDefaults { + return GrantedAuthorityDefaults("MYPREFIX_"); + } +} +---- + +.Xml +[source,xml,role="secondary"] +---- + + + + + +---- +==== + +[TIP] +==== +We expose `GrantedAuthorityDefaults` using a `static` method to ensure that Spring publishes it before it initializes Spring Security's method security `@Configuration` classes +==== + +[[jc-method-security-custom-authorization-manager]] +==== Custom Authorization Managers + +Method authorization is a combination of before- and after-method authorization. + +[NOTE] +==== +Before-method authorization is performed before the method is invoked. +If that authorization denies access, the method is not invoked, and an `AccessDeniedException` is thrown +After-method authorization is performed after the method is invoked, but before the method returns to the caller. +If that authorization denies access, the value is not returned, and an `AccessDeniedException` is thrown +==== + +To recreate what adding `@EnableMethodSecurity` does by default, you would publish the following configuration: + +.Full Pre-post Method Security Configuration +==== +.Java +[source,java,role="primary"] +---- +@EnableMethodSecurity(prePostEnabled = false) +class MethodSecurityConfig { + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + Advisor preFilterAuthorizationMethodInterceptor() { + return new PreFilterAuthorizationMethodInterceptor(); + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + Advisor preAuthorizeAuthorizationMethodInterceptor() { + return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(); + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + Advisor postAuthorizeAuthorizationMethodInterceptor() { + return AuthorizationManagerAfterMethodInterceptor.postAuthorize(); + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + Advisor postFilterAuthorizationMethodInterceptor() { + return new PostFilterAuthorizationMethodInterceptor(); + } +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +@EnableMethodSecurity(prePostEnabled = false) +class MethodSecurityConfig { + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + fun preFilterAuthorizationMethodInterceptor() : Advisor { + return PreFilterAuthorizationMethodInterceptor(); + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + fun preAuthorizeAuthorizationMethodInterceptor() : Advisor { + return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(); + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + fun postAuthorizeAuthorizationMethodInterceptor() : Advisor { + return AuthorizationManagerAfterMethodInterceptor.postAuthorize(); + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + fun postFilterAuthorizationMethodInterceptor() : Advisor { + return PostFilterAuthorizationMethodInterceptor(); + } +} +---- + +.Xml +[source,xml,role="secondary"] +---- + + + + + + + + +---- +==== + +Notice that Spring Security's method security is built using Spring AOP. +So, interceptors are invoked based on the order specified. +This can be customized by calling `setOrder` on the interceptor instances like so: + +.Publish Custom Advisor +==== +.Java +[source,java,role="primary"] +---- +@Bean +@Role(BeanDefinition.ROLE_INFRASTRUCTURE) +Advisor postFilterAuthorizationMethodInterceptor() { + PostFilterAuthorizationMethodInterceptor interceptor = new PostFilterAuthorizationMethodInterceptor(); + interceptor.setOrder(AuthorizationInterceptorOrders.POST_AUTHORIZE.getOrder() - 1); + return interceptor; +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +@Bean +@Role(BeanDefinition.ROLE_INFRASTRUCTURE) +fun postFilterAuthorizationMethodInterceptor() : Advisor { + val interceptor = PostFilterAuthorizationMethodInterceptor(); + interceptor.setOrder(AuthorizationInterceptorOrders.POST_AUTHORIZE.getOrder() - 1); + return interceptor; +} +---- + +.Xml +[source,xml,role="secondary"] +---- + + + +---- +==== + +You may want to only support `@PreAuthorize` in your application, in which case you can do the following: + + +.Only @PreAuthorize Configuration +==== +.Java +[source,java,role="primary"] +---- +@EnableMethodSecurity(prePostEnabled = false) +class MethodSecurityConfig { + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + Advisor preAuthorize() { + return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(); + } +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +@EnableMethodSecurity(prePostEnabled = false) +class MethodSecurityConfig { + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + fun preAuthorize() : Advisor { + return AuthorizationManagerBeforeMethodInterceptor.preAuthorize() + } +} +---- + +.Xml +[source,xml,role="secondary"] +---- + + + + + +---- +==== + +Or, you may have a custom before-method `AuthorizationManager` that you want to add to the list. + +In this case, you will need to tell Spring Security both the `AuthorizationManager` and to which methods and classes your authorization manager applies. + +Thus, you can configure Spring Security to invoke your `AuthorizationManager` in between `@PreAuthorize` and `@PostAuthorize` like so: + +.Custom Before Advisor +==== + +.Java +[source,java,role="primary"] +---- +@EnableMethodSecurity +class MethodSecurityConfig { + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public Advisor customAuthorize() { + JdkRegexpMethodPointcut pattern = new JdkRegexpMethodPointcut(); + pattern.setPattern("org.mycompany.myapp.service.*"); + AuthorizationManager rule = AuthorityAuthorizationManager.isAuthenticated(); + AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(pattern, rule); + interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1); + return interceptor; + } +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +@EnableMethodSecurity +class MethodSecurityConfig { + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + fun customAuthorize() : Advisor { + val pattern = JdkRegexpMethodPointcut(); + pattern.setPattern("org.mycompany.myapp.service.*"); + val rule = AuthorityAuthorizationManager.isAuthenticated(); + val interceptor = AuthorizationManagerBeforeMethodInterceptor(pattern, rule); + interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1); + return interceptor; + } +} +---- + +.Xml +[source,xml,role="secondary"] +---- + + + + + + + + + + + + + + + +---- +==== + +[TIP] +==== +You can place your interceptor in between Spring Security method interceptors using the order constants specified in `AuthorizationInterceptorsOrder`. +==== + +The same can be done for after-method authorization. +After-method authorization is generally concerned with analysing the return value to verify access. + +For example, you might have a method that confirms that the account requested actually belongs to the logged-in user like so: + +.@PostAuthorize example +==== +.Java +[source,java,role="primary"] +---- +public interface BankService { + + @PreAuthorize("hasRole('USER')") + @PostAuthorize("returnObject.owner == authentication.name") + Account readAccount(Long id); +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +interface BankService { + + @PreAuthorize("hasRole('USER')") + @PostAuthorize("returnObject.owner == authentication.name") + fun readAccount(id : Long) : Account +} +---- +==== + +You can supply your own `AuthorizationMethodInterceptor` to customize how access to the return value is evaluated. + +For example, if you have your own custom annotation, you can configure it like so: + + +.Custom After Advisor +==== +.Java +[source,java,role="primary"] +---- +@EnableMethodSecurity +class MethodSecurityConfig { + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public Advisor customAuthorize(AuthorizationManager rules) { + AnnotationMethodMatcher pattern = new AnnotationMethodMatcher(MySecurityAnnotation.class); + AuthorizationManagerAfterMethodInterceptor interceptor = new AuthorizationManagerAfterMethodInterceptor(pattern, rules); + interceptor.setOrder(AuthorizationInterceptorsOrder.POST_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1); + return interceptor; + } +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +@EnableMethodSecurity +class MethodSecurityConfig { + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + fun customAuthorize(rules : AuthorizationManager) : Advisor { + val pattern = AnnotationMethodMatcher(MySecurityAnnotation::class.java); + val interceptor = AuthorizationManagerAfterMethodInterceptor(pattern, rules); + interceptor.setOrder(AuthorizationInterceptorsOrder.POST_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1); + return interceptor; + } +} +---- + +.Xml +[source,xml,role="secondary"] +---- + + + + + + + + + + + + + + + +---- +==== + +and it will be invoked after the `@PostAuthorize` interceptor. + +[[jc-enable-global-method-security]] === EnableGlobalMethodSecurity We can enable annotation-based security using the `@EnableGlobalMethodSecurity` annotation on any `@Configuration` instance.