Add AspectJ AuthorizationManager Support
Closes gh-11326
This commit is contained in:
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
|
||||
/**
|
||||
* Registers an
|
||||
* {@link org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator
|
||||
* AnnotationAwareAspectJAutoProxyCreator} against the current
|
||||
* {@link BeanDefinitionRegistry} as appropriate based on a given @
|
||||
* {@link EnableMethodSecurity} annotation.
|
||||
*
|
||||
* <p>
|
||||
* Note: This class is necessary because AspectJAutoProxyRegistrar only supports
|
||||
* EnableAspectJAutoProxy.
|
||||
* </p>
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 5.8
|
||||
*/
|
||||
class MethodSecurityAspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
|
||||
|
||||
/**
|
||||
* Register, escalate, and configure the AspectJ auto proxy creator based on the value
|
||||
* of the @{@link EnableMethodSecurity#proxyTargetClass()} attribute on the importing
|
||||
* {@code @Configuration} class.
|
||||
*/
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
|
||||
registerBeanDefinition("preFilterAuthorizationMethodInterceptor",
|
||||
"org.springframework.security.authorization.method.aspectj.PreFilterAspect", "preFilterAspect$0",
|
||||
registry);
|
||||
registerBeanDefinition("postFilterAuthorizationMethodInterceptor",
|
||||
"org.springframework.security.authorization.method.aspectj.PostFilterAspect", "postFilterAspect$0",
|
||||
registry);
|
||||
registerBeanDefinition("preAuthorizeAuthorizationMethodInterceptor",
|
||||
"org.springframework.security.authorization.method.aspectj.PreAuthorizeAspect", "preAuthorizeAspect$0",
|
||||
registry);
|
||||
registerBeanDefinition("postAuthorizeAuthorizationMethodInterceptor",
|
||||
"org.springframework.security.authorization.method.aspectj.PostAuthorizeAspect",
|
||||
"postAuthorizeAspect$0", registry);
|
||||
registerBeanDefinition("securedAuthorizationMethodInterceptor",
|
||||
"org.springframework.security.authorization.method.aspectj.SecuredAspect", "securedAspect$0", registry);
|
||||
}
|
||||
|
||||
private void registerBeanDefinition(String beanName, String aspectClassName, String aspectBeanName,
|
||||
BeanDefinitionRegistry registry) {
|
||||
if (!registry.containsBeanDefinition(beanName)) {
|
||||
return;
|
||||
}
|
||||
BeanDefinition interceptor = registry.getBeanDefinition(beanName);
|
||||
BeanDefinitionBuilder aspect = BeanDefinitionBuilder.rootBeanDefinition(aspectClassName);
|
||||
aspect.setFactoryMethod("aspectOf");
|
||||
aspect.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
aspect.addPropertyValue("securityInterceptor", interceptor);
|
||||
registry.registerBeanDefinition(aspectBeanName, aspect.getBeanDefinition());
|
||||
}
|
||||
|
||||
}
|
||||
+7
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -62,11 +62,17 @@ final class MethodSecuritySelector implements ImportSelector {
|
||||
|
||||
private static final String[] IMPORTS = new String[] { AutoProxyRegistrar.class.getName() };
|
||||
|
||||
private static final String[] ASPECTJ_IMPORTS = new String[] {
|
||||
MethodSecurityAspectJAutoProxyRegistrar.class.getName() };
|
||||
|
||||
@Override
|
||||
protected String[] selectImports(@NonNull AdviceMode adviceMode) {
|
||||
if (adviceMode == AdviceMode.PROXY) {
|
||||
return IMPORTS;
|
||||
}
|
||||
if (adviceMode == AdviceMode.ASPECTJ) {
|
||||
return ASPECTJ_IMPORTS;
|
||||
}
|
||||
throw new IllegalStateException("AdviceMode '" + adviceMode + "' is not supported");
|
||||
}
|
||||
|
||||
|
||||
+55
-12
@@ -34,6 +34,7 @@ import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.ManagedMap;
|
||||
import org.springframework.beans.factory.xml.BeanDefinitionParser;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
@@ -78,6 +79,8 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
|
||||
|
||||
private static final String ATT_EXPRESSION = "expression";
|
||||
|
||||
private static final String ATT_MODE = "mode";
|
||||
|
||||
private static final String ATT_SECURITY_CONTEXT_HOLDER_STRATEGY_REF = "security-context-holder-strategy-ref";
|
||||
|
||||
@Override
|
||||
@@ -88,6 +91,7 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
|
||||
BeanMetadataElement securityContextHolderStrategy = getSecurityContextHolderStrategy(element);
|
||||
boolean prePostAnnotationsEnabled = !element.hasAttribute(ATT_USE_PREPOST)
|
||||
|| "true".equals(element.getAttribute(ATT_USE_PREPOST));
|
||||
boolean useAspectJ = "aspectj".equals(element.getAttribute(ATT_MODE));
|
||||
if (prePostAnnotationsEnabled) {
|
||||
BeanDefinitionBuilder preFilterInterceptor = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(PreFilterAuthorizationMethodInterceptor.class)
|
||||
@@ -151,20 +155,29 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
|
||||
}
|
||||
Map<Pointcut, BeanMetadataElement> managers = new ManagedMap<>();
|
||||
List<Element> methods = DomUtils.getChildElementsByTagName(element, Elements.PROTECT_POINTCUT);
|
||||
if (!methods.isEmpty()) {
|
||||
for (Element protectElt : methods) {
|
||||
managers.put(pointcut(protectElt), authorizationManager(element, protectElt));
|
||||
if (useAspectJ) {
|
||||
if (!methods.isEmpty()) {
|
||||
pc.getReaderContext().error("Cannot use <protect-pointcut> and mode='aspectj' together",
|
||||
pc.extractSource(element));
|
||||
}
|
||||
BeanDefinitionBuilder protectPointcutInterceptor = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(AuthorizationManagerBeforeMethodInterceptor.class)
|
||||
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy)
|
||||
.addConstructorArgValue(pointcut(managers.keySet()))
|
||||
.addConstructorArgValue(authorizationManager(managers));
|
||||
pc.getRegistry().registerBeanDefinition("protectPointcutInterceptor",
|
||||
protectPointcutInterceptor.getBeanDefinition());
|
||||
registerInterceptors(pc.getRegistry());
|
||||
}
|
||||
else {
|
||||
if (!methods.isEmpty()) {
|
||||
for (Element protectElt : methods) {
|
||||
managers.put(pointcut(protectElt), authorizationManager(element, protectElt));
|
||||
}
|
||||
BeanDefinitionBuilder protectPointcutInterceptor = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(AuthorizationManagerBeforeMethodInterceptor.class)
|
||||
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy)
|
||||
.addConstructorArgValue(pointcut(managers.keySet()))
|
||||
.addConstructorArgValue(authorizationManager(managers));
|
||||
pc.getRegistry().registerBeanDefinition("protectPointcutInterceptor",
|
||||
protectPointcutInterceptor.getBeanDefinition());
|
||||
}
|
||||
AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(pc, element);
|
||||
}
|
||||
AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(pc, element);
|
||||
pc.popAndRegisterContainingComponent();
|
||||
return null;
|
||||
}
|
||||
@@ -218,6 +231,36 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
|
||||
.addConstructorArgValue(managers).getBeanDefinition();
|
||||
}
|
||||
|
||||
private void registerInterceptors(BeanDefinitionRegistry registry) {
|
||||
registerBeanDefinition("preFilterAuthorizationMethodInterceptor",
|
||||
"org.springframework.security.authorization.method.aspectj.PreFilterAspect", "preFilterAspect$0",
|
||||
registry);
|
||||
registerBeanDefinition("postFilterAuthorizationMethodInterceptor",
|
||||
"org.springframework.security.authorization.method.aspectj.PostFilterAspect", "postFilterAspect$0",
|
||||
registry);
|
||||
registerBeanDefinition("preAuthorizeAuthorizationMethodInterceptor",
|
||||
"org.springframework.security.authorization.method.aspectj.PreAuthorizeAspect", "preAuthorizeAspect$0",
|
||||
registry);
|
||||
registerBeanDefinition("postAuthorizeAuthorizationMethodInterceptor",
|
||||
"org.springframework.security.authorization.method.aspectj.PostAuthorizeAspect",
|
||||
"postAuthorizeAspect$0", registry);
|
||||
registerBeanDefinition("securedAuthorizationMethodInterceptor",
|
||||
"org.springframework.security.authorization.method.aspectj.SecuredAspect", "securedAspect$0", registry);
|
||||
}
|
||||
|
||||
private void registerBeanDefinition(String beanName, String aspectClassName, String aspectBeanName,
|
||||
BeanDefinitionRegistry registry) {
|
||||
if (!registry.containsBeanDefinition(beanName)) {
|
||||
return;
|
||||
}
|
||||
BeanDefinition interceptor = registry.getBeanDefinition(beanName);
|
||||
BeanDefinitionBuilder aspect = BeanDefinitionBuilder.rootBeanDefinition(aspectClassName);
|
||||
aspect.setFactoryMethod("aspectOf");
|
||||
aspect.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
aspect.addPropertyValue("securityInterceptor", interceptor);
|
||||
registry.registerBeanDefinition(aspectBeanName, aspect.getBeanDefinition());
|
||||
}
|
||||
|
||||
public static final class MethodSecurityExpressionHandlerBean
|
||||
implements FactoryBean<MethodSecurityExpressionHandler>, ApplicationContextAware {
|
||||
|
||||
|
||||
@@ -216,6 +216,9 @@ method-security.attlist &=
|
||||
method-security.attlist &=
|
||||
## If true, class-based proxying will be used instead of interface-based proxying.
|
||||
attribute proxy-target-class {xsd:boolean}?
|
||||
method-security.attlist &=
|
||||
## If set to aspectj, then use AspectJ to intercept method invocation
|
||||
attribute mode {"aspectj"}?
|
||||
method-security.attlist &=
|
||||
## Specifies the security context holder strategy to use, by default uses a ThreadLocal-based strategy
|
||||
attribute security-context-holder-strategy-ref {xsd:string}?
|
||||
|
||||
@@ -677,6 +677,17 @@
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="mode">
|
||||
<xs:annotation>
|
||||
<xs:documentation>If set to aspectj, then use AspectJ to intercept method invocation
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:enumeration value="aspectj"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="security-context-holder-strategy-ref" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Specifies the security context holder strategy to use, by default uses a ThreadLocal-based
|
||||
|
||||
+27
@@ -32,6 +32,7 @@ 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.AdviceMode;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Role;
|
||||
@@ -406,6 +407,17 @@ public class PrePostMethodSecurityConfigurationTests {
|
||||
this.methodSecurityService.preAuthorizeBean(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenAspectJThenRegistersAspects() {
|
||||
this.spring.register(AspectJMethodSecurityServiceConfig.class).autowire();
|
||||
assertThat(this.spring.getContext().containsBean("preFilterAspect$0")).isTrue();
|
||||
assertThat(this.spring.getContext().containsBean("postFilterAspect$0")).isTrue();
|
||||
assertThat(this.spring.getContext().containsBean("preAuthorizeAspect$0")).isTrue();
|
||||
assertThat(this.spring.getContext().containsBean("postAuthorizeAspect$0")).isTrue();
|
||||
assertThat(this.spring.getContext().containsBean("securedAspect$0")).isTrue();
|
||||
assertThat(this.spring.getContext().containsBean("annotationSecurityAspect$0")).isFalse();
|
||||
}
|
||||
|
||||
@EnableMethodSecurity
|
||||
static class MethodSecurityServiceConfig {
|
||||
|
||||
@@ -547,4 +559,19 @@ public class PrePostMethodSecurityConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@EnableMethodSecurity(mode = AdviceMode.ASPECTJ, securedEnabled = true)
|
||||
static class AspectJMethodSecurityServiceConfig {
|
||||
|
||||
@Bean
|
||||
MethodSecurityService methodSecurityService() {
|
||||
return new MethodSecurityServiceImpl();
|
||||
}
|
||||
|
||||
@Bean
|
||||
Authz authz() {
|
||||
return new Authz();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+11
@@ -85,6 +85,17 @@ public class MethodSecurityBeanDefinitionParserTests {
|
||||
.withMessage("Access Denied");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenAspectJThenRegistersAspects() {
|
||||
this.spring.configLocations(xml("AspectJMethodSecurityServiceEnabled")).autowire();
|
||||
assertThat(this.spring.getContext().containsBean("preFilterAspect$0")).isTrue();
|
||||
assertThat(this.spring.getContext().containsBean("postFilterAspect$0")).isTrue();
|
||||
assertThat(this.spring.getContext().containsBean("preAuthorizeAspect$0")).isTrue();
|
||||
assertThat(this.spring.getContext().containsBean("postAuthorizeAspect$0")).isTrue();
|
||||
assertThat(this.spring.getContext().containsBean("securedAspect$0")).isTrue();
|
||||
assertThat(this.spring.getContext().containsBean("annotationSecurityAspect$0")).isFalse();
|
||||
}
|
||||
|
||||
@WithAnonymousUser
|
||||
@Test
|
||||
public void preAuthorizePermitAllWhenRoleAnonymousThenPasses() {
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<method-security mode="aspectj" secured-enabled="true" jsr250-enabled="true"/>
|
||||
|
||||
<b:bean class="org.springframework.security.config.annotation.method.configuration.MethodSecurityServiceImpl"/>
|
||||
|
||||
</b:beans>
|
||||
Reference in New Issue
Block a user