1
0
mirror of synced 2026-05-22 21:33:16 +00:00

Add AspectJ AuthorizationManager Support

Closes gh-11326
This commit is contained in:
Josh Cummings
2022-07-08 11:37:36 -06:00
parent a5351f3d89
commit 0f58620643
24 changed files with 1223 additions and 13 deletions
@@ -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());
}
}
@@ -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");
}
@@ -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
@@ -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();
}
}
}
@@ -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() {
@@ -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>