INT-3284: Add 'spring.factories' Infrastructure

JIRA: https://jira.springsource.org/browse/INT-3284

* Introduce `IntegrationConfigurationBeanFactoryPostProcessor`, `IntegrationConfigurationInitializer`
* Apply `IntegrationConfigurationInitializer` and 'spring.factories' for HTTP and Security modules

INT-3284: Address PR's comments

JIRA: https://jira.springsource.org/browse/INT-3287

* Fix package tangle (INT-3287)
* Apply `IntegrationConfigurationInitializer` for `MBeanExporterHelper`
* Previously, there was a separate `ChannelSecurityInterceptorBeanPostProcessor`
for each `ChannelSecurityInterceptor` whereas now, there is one `ChannelSecurityInterceptorBeanPostProcessor`
that processes all interceptors

INT-3284 Refactoring around config package tangle
This commit is contained in:
Artem Bilan
2014-02-05 13:57:40 +02:00
committed by Gary Russell
parent b59fe444cf
commit ea080e8a9b
50 changed files with 513 additions and 225 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2014 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.
@@ -16,52 +16,61 @@
package org.springframework.integration.security.config;
import java.util.Collection;
import java.util.Set;
import java.util.regex.Pattern;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.messaging.MessageChannel;
import org.springframework.integration.security.channel.ChannelSecurityMetadataSource;
import org.springframework.integration.security.channel.ChannelSecurityInterceptor;
import org.springframework.util.Assert;
import org.springframework.integration.security.channel.ChannelSecurityMetadataSource;
import org.springframework.messaging.MessageChannel;
/**
* A {@link BeanPostProcessor} that proxies {@link MessageChannel}s to apply a {@link ChannelSecurityInterceptor}.
*
*
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Artem Bilan
*/
public class ChannelSecurityInterceptorBeanPostProcessor implements BeanPostProcessor {
private final ChannelSecurityInterceptor interceptor;
private final Collection<ChannelSecurityInterceptor> securityInterceptors;
public ChannelSecurityInterceptorBeanPostProcessor(ChannelSecurityInterceptor interceptor) {
Assert.notNull(interceptor, "interceptor must not be null");
this.interceptor = interceptor;
public ChannelSecurityInterceptorBeanPostProcessor(Collection<ChannelSecurityInterceptor> securityInterceptors) {
this.securityInterceptors = securityInterceptors;
}
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
ChannelSecurityMetadataSource channelSecurityMetadataSource =
(ChannelSecurityMetadataSource) this.interceptor.obtainSecurityMetadataSource();
if (bean instanceof MessageChannel &&
shouldProxy(beanName, (MessageChannel) bean, channelSecurityMetadataSource)) {
ProxyFactory proxyFactory = new ProxyFactory(bean);
proxyFactory.addAdvisor(new DefaultPointcutAdvisor(this.interceptor));
return proxyFactory.getProxy();
if (bean instanceof MessageChannel) {
for (ChannelSecurityInterceptor securityInterceptor : securityInterceptors) {
ChannelSecurityMetadataSource channelSecurityMetadataSource =
(ChannelSecurityMetadataSource) securityInterceptor.obtainSecurityMetadataSource();
if (this.shouldProxy(beanName, channelSecurityMetadataSource)) {
if (AopUtils.isAopProxy(bean) && bean instanceof Advised) {
((Advised) bean).addAdvisor(new DefaultPointcutAdvisor(securityInterceptor));
}
else {
ProxyFactory proxyFactory = new ProxyFactory(bean);
proxyFactory.addAdvisor(new DefaultPointcutAdvisor(securityInterceptor));
bean = proxyFactory.getProxy();
}
}
}
}
return bean;
}
private boolean shouldProxy(String beanName, MessageChannel channel, ChannelSecurityMetadataSource channelSecurityMetadataSource) {
private boolean shouldProxy(String beanName, ChannelSecurityMetadataSource channelSecurityMetadataSource) {
Set<Pattern> patterns = channelSecurityMetadataSource.getPatterns();
for (Pattern pattern : patterns) {
if (pattern.matcher(beanName).matches()) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2014 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.
@@ -23,11 +23,13 @@ import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.security.channel.ChannelSecurityInterceptor;
import org.springframework.integration.security.channel.ChannelSecurityMetadataSource;
import org.springframework.integration.security.channel.DefaultChannelAccessPolicy;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
@@ -36,18 +38,16 @@ import org.springframework.util.xml.DomUtils;
* to control send and receive access, and creates a bean post-processor to apply the
* interceptor to {@link org.springframework.messaging.MessageChannel}s
* whose names match the specified patterns.
*
*
* @author Jonas Partner
* @author Mark Fisher
* @author Artem Bilan
*/
public class SecuredChannelsParser extends AbstractSingleBeanDefinitionParser {
private final static String BASE_PACKAGE_NAME = "org.springframework.integration.security";
@Override
protected String getBeanClassName(Element element) {
return BASE_PACKAGE_NAME + ".config.ChannelSecurityInterceptorBeanPostProcessor";
protected Class<?> getBeanClass(Element element) {
return ChannelSecurityInterceptor.class;
}
@Override
@@ -57,23 +57,15 @@ public class SecuredChannelsParser extends AbstractSingleBeanDefinitionParser {
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
String securityMetadataSourceBeanName = this.parseSecurityMetadataSource(element, parserContext);
BeanDefinitionBuilder interceptorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
BASE_PACKAGE_NAME + ".channel.ChannelSecurityInterceptor");
interceptorBuilder.addConstructorArgReference(securityMetadataSourceBeanName);
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(interceptorBuilder, element, "authentication-manager");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(interceptorBuilder, element, "access-decision-manager");
String interceptorBeanName = BeanDefinitionReaderUtils.registerWithGeneratedName(
interceptorBuilder.getBeanDefinition(), parserContext.getRegistry());
builder.addConstructorArgReference(interceptorBeanName);
builder.addConstructorArgValue(this.parseSecurityMetadataSource(element, parserContext));
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "authentication-manager");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "access-decision-manager");
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private String parseSecurityMetadataSource(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
BASE_PACKAGE_NAME + ".channel.ChannelSecurityMetadataSource");
private BeanDefinition parseSecurityMetadataSource(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ChannelSecurityMetadataSource.class);
List<Element> accessPolicyElements = DomUtils.getChildElementsByTagName(element, "access-policy");
ManagedMap patternMappings = new ManagedMap();
ManagedMap<Pattern, BeanDefinition> patternMappings = new ManagedMap<Pattern, BeanDefinition>();
for (Element accessPolicyElement : accessPolicyElements) {
Pattern pattern = Pattern.compile(accessPolicyElement.getAttribute("pattern"));
String sendAccess = accessPolicyElement.getAttribute("send-access");
@@ -82,17 +74,15 @@ public class SecuredChannelsParser extends AbstractSingleBeanDefinitionParser {
parserContext.getReaderContext().error(
"At least one of 'send-access' or 'receive-access' must be provided.", accessPolicyElement);
}
BeanDefinitionBuilder accessPolicyBuilder = BeanDefinitionBuilder.genericBeanDefinition(
BASE_PACKAGE_NAME + ".channel.DefaultChannelAccessPolicy");
BeanDefinitionBuilder accessPolicyBuilder = BeanDefinitionBuilder.genericBeanDefinition(DefaultChannelAccessPolicy.class);
accessPolicyBuilder.addConstructorArgValue(sendAccess);
accessPolicyBuilder.addConstructorArgValue(receiveAccess);
accessPolicyBuilder.getBeanDefinition().setRole(BeanDefinition.ROLE_SUPPORT);
patternMappings.put(pattern, accessPolicyBuilder.getBeanDefinition());
}
builder.addConstructorArgValue(patternMappings);
builder.setRole(BeanDefinition.ROLE_SUPPORT);
return BeanDefinitionReaderUtils.registerWithGeneratedName(
builder.getBeanDefinition(), parserContext.getRegistry());
return builder.getBeanDefinition();
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2014 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
*
* http://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.integration.security.config;
import java.util.Collection;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.integration.config.IntegrationConfigurationInitializer;
import org.springframework.integration.security.channel.ChannelSecurityInterceptor;
/**
* The Integration Security infrastructure {@code beanFactory} initializer.
*
* @author Artem Bilan
* @since 4.0
*/
public class SecurityIntegrationConfigurationInitializer implements IntegrationConfigurationInitializer {
@Override
public void initialize(ConfigurableListableBeanFactory beanFactory) throws BeansException {
Collection<ChannelSecurityInterceptor> securityInterceptors = beanFactory.getBeansOfType(ChannelSecurityInterceptor.class).values();
beanFactory.addBeanPostProcessor(new ChannelSecurityInterceptorBeanPostProcessor(securityInterceptors));
}
}

View File

@@ -0,0 +1,2 @@
org.springframework.integration.config.IntegrationConfigurationInitializer=\
org.springframework.integration.security.config.SecurityIntegrationConfigurationInitializer

View File

@@ -26,6 +26,8 @@
<outbound-channel-adapter id="securedChannelAdapter" ref="testHandler"/>
<service-activator input-channel="securedChannelAdapter2" ref="testHandler"/>
<outbound-channel-adapter id="unsecuredChannelAdapter" ref="testHandler"/>
</beans:beans>
</beans:beans>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2014 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.
@@ -37,6 +37,7 @@ import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
/**
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Artem Bilan
*/
@ContextConfiguration
public class ChannelAdapterSecurityIntegrationTests extends AbstractJUnit4SpringContextTests {
@@ -45,6 +46,10 @@ public class ChannelAdapterSecurityIntegrationTests extends AbstractJUnit4Spring
@Qualifier("securedChannelAdapter")
MessageChannel securedChannelAdapter;
@Autowired
@Qualifier("securedChannelAdapter")
MessageChannel securedChannelAdapter2;
@Autowired
@Qualifier("unsecuredChannelAdapter")
MessageChannel unsecuredChannelAdapter;
@@ -65,13 +70,14 @@ public class ChannelAdapterSecurityIntegrationTests extends AbstractJUnit4Spring
login("bob", "bobspassword", "ROLE_ADMINA");
securedChannelAdapter.send(new GenericMessage<String>("test"));
}
@Test
@DirtiesContext
public void testSecuredWithPermission() {
login("bob", "bobspassword", "ROLE_ADMIN", "ROLE_PRESIDENT");
securedChannelAdapter.send(new GenericMessage<String>("test"));
assertEquals("Wrong size of message list in target", 1, testConsumer.sentMessages.size());
securedChannelAdapter2.send(new GenericMessage<String>("test"));
assertEquals("Wrong size of message list in target", 2, testConsumer.sentMessages.size());
}
@Test(expected = AccessDeniedException.class)
@@ -81,6 +87,13 @@ public class ChannelAdapterSecurityIntegrationTests extends AbstractJUnit4Spring
securedChannelAdapter.send(new GenericMessage<String>("test"));
}
@Test(expected = AccessDeniedException.class)
@DirtiesContext
public void testSecured2WithoutPermision() {
login("bob", "bobspassword", "ROLE_USER");
securedChannelAdapter2.send(new GenericMessage<String>("test"));
}
@Test(expected = AuthenticationException.class)
@DirtiesContext
public void testSecuredWithoutAuthenticating() {

View File

@@ -19,6 +19,7 @@ package org.springframework.integration.security.channel;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.Collections;
import java.util.regex.Pattern;
import org.junit.Test;
@@ -38,7 +39,7 @@ public class ChannelSecurityInterceptorBeanPostProcessorTests {
ChannelSecurityMetadataSource securityMetadataSource = new ChannelSecurityMetadataSource();
securityMetadataSource.addPatternMapping(Pattern.compile("secured.*"), new DefaultChannelAccessPolicy("ROLE_ADMIN", null));
ChannelSecurityInterceptor interceptor = new ChannelSecurityInterceptor(securityMetadataSource);
ChannelSecurityInterceptorBeanPostProcessor postProcessor = new ChannelSecurityInterceptorBeanPostProcessor(interceptor);
ChannelSecurityInterceptorBeanPostProcessor postProcessor = new ChannelSecurityInterceptorBeanPostProcessor(Collections.singletonList(interceptor));
QueueChannel securedChannel = new QueueChannel();
securedChannel.setBeanName("securedChannel");
MessageChannel postProcessedChannel = (MessageChannel) postProcessor.postProcessAfterInitialization(securedChannel, "securedChannel");
@@ -50,7 +51,7 @@ public class ChannelSecurityInterceptorBeanPostProcessorTests {
ChannelSecurityMetadataSource securityMetadataSource = new ChannelSecurityMetadataSource();
securityMetadataSource.addPatternMapping(Pattern.compile("secured.*"), new DefaultChannelAccessPolicy("ROLE_ADMIN", null));
ChannelSecurityInterceptor interceptor = new ChannelSecurityInterceptor(securityMetadataSource);
ChannelSecurityInterceptorBeanPostProcessor postProcessor = new ChannelSecurityInterceptorBeanPostProcessor(interceptor);
ChannelSecurityInterceptorBeanPostProcessor postProcessor = new ChannelSecurityInterceptorBeanPostProcessor(Collections.singletonList(interceptor));
QueueChannel channel = new QueueChannel();
channel.setBeanName("testChannel");
MessageChannel postProcessedChannel = (MessageChannel) postProcessor.postProcessAfterInitialization(channel, "testChannel");