From e3f8ef534bdc88daf8d536c63158caeb7d14dbdf Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Thu, 20 Mar 2014 11:58:22 +0200 Subject: [PATCH] INT-3331: Add `ChannelSecurityInterceptorFB` JIRA: https://jira.spring.io/browse/INT-3331 INT-3331: PR comments and others * Register `ChannelSecurityInterceptorBeanPostProcessor` as a `BeanDefinition` (not `BPP`) * Get `ChannelSecurityInterceptor`s from `ChannelSecurityInterceptorBeanPostProcessor#afterPropertiesSet()` * Make `ChannelSecurityInterceptor` `final` to disallow to subclass it for unexpected issues * Provide more convenience to the `ChannelSecurityInterceptorFactoryBean` - to allow to use it from xml configuration Doc Polishing --- .../channel/ChannelSecurityInterceptor.java | 8 +- ...lSecurityInterceptorBeanPostProcessor.java | 22 ++- ...ChannelSecurityInterceptorFactoryBean.java | 144 +++++++++++++++ .../config/SecuredChannelsParser.java | 6 +- ...tyIntegrationConfigurationInitializer.java | 13 +- .../security/{channel => }/TestHandler.java | 2 +- ...dapterSecurityIntegrationTests-context.xml | 10 +- ...hannelAdapterSecurityIntegrationTests.java | 1 + ...rityInterceptorBeanPostProcessorTests.java | 46 ++++- ...elSecurityInterceptorFactoryBeanTests.java | 167 ++++++++++++++++++ src/reference/docbook/security.xml | 32 ++++ src/reference/docbook/whats-new.xml | 8 + 12 files changed, 425 insertions(+), 34 deletions(-) create mode 100644 spring-integration-security/src/main/java/org/springframework/integration/security/config/ChannelSecurityInterceptorFactoryBean.java rename spring-integration-security/src/test/java/org/springframework/integration/security/{channel => }/TestHandler.java (94%) create mode 100644 spring-integration-security/src/test/java/org/springframework/integration/security/config/ChannelSecurityInterceptorFactoryBeanTests.java diff --git a/spring-integration-security/src/main/java/org/springframework/integration/security/channel/ChannelSecurityInterceptor.java b/spring-integration-security/src/main/java/org/springframework/integration/security/channel/ChannelSecurityInterceptor.java index 235a9b626a..0e9b4e6317 100644 --- a/spring-integration-security/src/main/java/org/springframework/integration/security/channel/ChannelSecurityInterceptor.java +++ b/spring-integration-security/src/main/java/org/springframework/integration/security/channel/ChannelSecurityInterceptor.java @@ -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. @@ -24,14 +24,14 @@ import org.springframework.security.access.SecurityMetadataSource; import org.springframework.security.access.intercept.AbstractSecurityInterceptor; import org.springframework.security.access.intercept.InterceptorStatusToken; import org.springframework.util.Assert; - + /** * An AOP interceptor that enforces authorization for MessageChannel send and/or receive calls. - * + * * @author Mark Fisher * @author Oleg Zhurakousky */ -public class ChannelSecurityInterceptor extends AbstractSecurityInterceptor implements MethodInterceptor { +public final class ChannelSecurityInterceptor extends AbstractSecurityInterceptor implements MethodInterceptor { private final ChannelSecurityMetadataSource securityMetadataSource; diff --git a/spring-integration-security/src/main/java/org/springframework/integration/security/config/ChannelSecurityInterceptorBeanPostProcessor.java b/spring-integration-security/src/main/java/org/springframework/integration/security/config/ChannelSecurityInterceptorBeanPostProcessor.java index d8851dc033..aae289161e 100644 --- a/spring-integration-security/src/main/java/org/springframework/integration/security/config/ChannelSecurityInterceptorBeanPostProcessor.java +++ b/spring-integration-security/src/main/java/org/springframework/integration/security/config/ChannelSecurityInterceptorBeanPostProcessor.java @@ -25,10 +25,15 @@ 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.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.integration.security.channel.ChannelSecurityInterceptor; import org.springframework.integration.security.channel.ChannelSecurityMetadataSource; import org.springframework.messaging.MessageChannel; +import org.springframework.util.Assert; /** * A {@link BeanPostProcessor} that proxies {@link MessageChannel}s to apply a {@link ChannelSecurityInterceptor}. @@ -37,13 +42,21 @@ import org.springframework.messaging.MessageChannel; * @author Oleg Zhurakousky * @author Artem Bilan */ -public class ChannelSecurityInterceptorBeanPostProcessor implements BeanPostProcessor { +public class ChannelSecurityInterceptorBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware, InitializingBean { - private final Collection securityInterceptors; + private volatile Collection securityInterceptors; + private ListableBeanFactory beanFactory; - public ChannelSecurityInterceptorBeanPostProcessor(Collection securityInterceptors) { - this.securityInterceptors = securityInterceptors; + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + Assert.isInstanceOf(ListableBeanFactory.class, beanFactory); + this.beanFactory = (ListableBeanFactory) beanFactory; + } + + @Override + public void afterPropertiesSet() throws Exception { + this.securityInterceptors = this.beanFactory.getBeansOfType(ChannelSecurityInterceptor.class).values(); } public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { @@ -79,5 +92,4 @@ public class ChannelSecurityInterceptorBeanPostProcessor implements BeanPostProc } return false; } - } diff --git a/spring-integration-security/src/main/java/org/springframework/integration/security/config/ChannelSecurityInterceptorFactoryBean.java b/spring-integration-security/src/main/java/org/springframework/integration/security/config/ChannelSecurityInterceptorFactoryBean.java new file mode 100644 index 0000000000..2ed5d438f7 --- /dev/null +++ b/spring-integration-security/src/main/java/org/springframework/integration/security/config/ChannelSecurityInterceptorFactoryBean.java @@ -0,0 +1,144 @@ +/* + * 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.Map; +import java.util.regex.Pattern; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.integration.security.channel.ChannelSecurityInterceptor; +import org.springframework.integration.security.channel.ChannelSecurityMetadataSource; +import org.springframework.integration.security.channel.DefaultChannelAccessPolicy; +import org.springframework.security.access.AccessDecisionManager; +import org.springframework.security.access.intercept.AfterInvocationManager; +import org.springframework.security.access.intercept.RunAsManager; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.util.Assert; + +/** + * The {@link FactoryBean} for {@code } JavaConfig variant to provide options + * for {@link ChannelSecurityInterceptor} beans. + * + * @author Artem Bilan + * @since 4.0 + */ +public class ChannelSecurityInterceptorFactoryBean implements FactoryBean, BeanNameAware, BeanFactoryAware { + + private final ChannelSecurityInterceptor interceptor = new ChannelSecurityInterceptor(new ChannelSecurityMetadataSource()); + + private BeanFactory beanFactory; + + private String name; + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + if (this.interceptor.getAuthenticationManager() == null && beanFactory.containsBean("authenticationManager")) { + this.interceptor.setAuthenticationManager(beanFactory.getBean("authenticationManager", AuthenticationManager.class)); + } + if (this.interceptor.getAccessDecisionManager() == null && beanFactory.containsBean("accessDecisionManager")) { + this.interceptor.setAccessDecisionManager(beanFactory.getBean("accessDecisionManager", AccessDecisionManager.class)); + } + } + + @Override + public void setBeanName(String name) { + this.name = name; + } + + public ChannelSecurityInterceptorFactoryBean setAccessDecisionManager(AccessDecisionManager accessDecisionManager) { + interceptor.setAccessDecisionManager(accessDecisionManager); + return this; + } + + public ChannelSecurityInterceptorFactoryBean setAfterInvocationManager(AfterInvocationManager afterInvocationManager) { + interceptor.setAfterInvocationManager(afterInvocationManager); + return this; + } + + public ChannelSecurityInterceptorFactoryBean setAlwaysReauthenticate(boolean alwaysReauthenticate) { + interceptor.setAlwaysReauthenticate(alwaysReauthenticate); + return this; + } + + public ChannelSecurityInterceptorFactoryBean setAuthenticationManager(AuthenticationManager newManager) { + interceptor.setAuthenticationManager(newManager); + return this; + } + + public ChannelSecurityInterceptorFactoryBean setPublishAuthorizationSuccess(boolean publishAuthorizationSuccess) { + interceptor.setPublishAuthorizationSuccess(publishAuthorizationSuccess); + return this; + } + + public ChannelSecurityInterceptorFactoryBean setRejectPublicInvocations(boolean rejectPublicInvocations) { + interceptor.setRejectPublicInvocations(rejectPublicInvocations); + return this; + } + + public ChannelSecurityInterceptorFactoryBean setRunAsManager(RunAsManager runAsManager) { + interceptor.setRunAsManager(runAsManager); + return this; + } + + public ChannelSecurityInterceptorFactoryBean setValidateConfigAttributes(boolean validateConfigAttributes) { + interceptor.setValidateConfigAttributes(validateConfigAttributes); + return this; + } + + public ChannelSecurityInterceptorFactoryBean accessPolicy(String pattern, String sendAccess) { + return this.accessPolicy(pattern, sendAccess, null); + } + + public ChannelSecurityInterceptorFactoryBean accessPolicy(String pattern, String sendAccess, String receiveAccess) { + Assert.hasText(pattern); + ((ChannelSecurityMetadataSource) interceptor.obtainSecurityMetadataSource()) + .addPatternMapping(Pattern.compile(pattern), new DefaultChannelAccessPolicy(sendAccess, receiveAccess)); + return this; + } + + public ChannelSecurityInterceptorFactoryBean setAccessPolicies(Map accessPolicies) { + Assert.notNull(accessPolicies); + ChannelSecurityMetadataSource channelSecurityMetadataSource = (ChannelSecurityMetadataSource) interceptor.obtainSecurityMetadataSource(); + for (Map.Entry entry : accessPolicies.entrySet()) { + channelSecurityMetadataSource.addPatternMapping(Pattern.compile(entry.getKey()), entry.getValue()); + } + return this; + } + + @Override + public ChannelSecurityInterceptor getObject() throws Exception { + ((AutowireCapableBeanFactory) this.beanFactory).initializeBean(this.interceptor, this.name); + return this.interceptor; + } + + @Override + public Class getObjectType() { + return ChannelSecurityInterceptor.class; + } + + @Override + public boolean isSingleton() { + return true; + } + +} diff --git a/spring-integration-security/src/main/java/org/springframework/integration/security/config/SecuredChannelsParser.java b/spring-integration-security/src/main/java/org/springframework/integration/security/config/SecuredChannelsParser.java index 6565596af9..8f670de238 100644 --- a/spring-integration-security/src/main/java/org/springframework/integration/security/config/SecuredChannelsParser.java +++ b/spring-integration-security/src/main/java/org/springframework/integration/security/config/SecuredChannelsParser.java @@ -17,7 +17,6 @@ package org.springframework.integration.security.config; import java.util.List; -import java.util.regex.Pattern; import org.w3c.dom.Element; @@ -65,9 +64,8 @@ public class SecuredChannelsParser extends AbstractSingleBeanDefinitionParser { private BeanDefinition parseSecurityMetadataSource(Element element, ParserContext parserContext) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ChannelSecurityMetadataSource.class); List accessPolicyElements = DomUtils.getChildElementsByTagName(element, "access-policy"); - ManagedMap patternMappings = new ManagedMap(); + ManagedMap patternMappings = new ManagedMap(); for (Element accessPolicyElement : accessPolicyElements) { - Pattern pattern = Pattern.compile(accessPolicyElement.getAttribute("pattern")); String sendAccess = accessPolicyElement.getAttribute("send-access"); String receiveAccess = accessPolicyElement.getAttribute("receive-access"); if (!StringUtils.hasText(sendAccess) && !StringUtils.hasText(receiveAccess)) { @@ -78,7 +76,7 @@ public class SecuredChannelsParser extends AbstractSingleBeanDefinitionParser { accessPolicyBuilder.addConstructorArgValue(sendAccess); accessPolicyBuilder.addConstructorArgValue(receiveAccess); accessPolicyBuilder.getBeanDefinition().setRole(BeanDefinition.ROLE_SUPPORT); - patternMappings.put(pattern, accessPolicyBuilder.getBeanDefinition()); + patternMappings.put(accessPolicyElement.getAttribute("pattern"), accessPolicyBuilder.getBeanDefinition()); } builder.addConstructorArgValue(patternMappings); diff --git a/spring-integration-security/src/main/java/org/springframework/integration/security/config/SecurityIntegrationConfigurationInitializer.java b/spring-integration-security/src/main/java/org/springframework/integration/security/config/SecurityIntegrationConfigurationInitializer.java index fbb745a154..4c54b629fa 100644 --- a/spring-integration-security/src/main/java/org/springframework/integration/security/config/SecurityIntegrationConfigurationInitializer.java +++ b/spring-integration-security/src/main/java/org/springframework/integration/security/config/SecurityIntegrationConfigurationInitializer.java @@ -16,12 +16,11 @@ 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.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.integration.config.IntegrationConfigurationInitializer; -import org.springframework.integration.security.channel.ChannelSecurityInterceptor; /** * The Integration Security infrastructure {@code beanFactory} initializer. @@ -31,10 +30,14 @@ import org.springframework.integration.security.channel.ChannelSecurityIntercept */ public class SecurityIntegrationConfigurationInitializer implements IntegrationConfigurationInitializer { + private static final String CHANNEL_SECURITY_INTERCEPTOR_BPP_BEAN_NAME = ChannelSecurityInterceptorBeanPostProcessor.class.getName(); + @Override public void initialize(ConfigurableListableBeanFactory beanFactory) throws BeansException { - Collection securityInterceptors = beanFactory.getBeansOfType(ChannelSecurityInterceptor.class).values(); - beanFactory.addBeanPostProcessor(new ChannelSecurityInterceptorBeanPostProcessor(securityInterceptors)); + BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; + if (!registry.containsBeanDefinition(CHANNEL_SECURITY_INTERCEPTOR_BPP_BEAN_NAME)) { + registry.registerBeanDefinition(CHANNEL_SECURITY_INTERCEPTOR_BPP_BEAN_NAME, new RootBeanDefinition(ChannelSecurityInterceptorBeanPostProcessor.class)); + } } } diff --git a/spring-integration-security/src/test/java/org/springframework/integration/security/channel/TestHandler.java b/spring-integration-security/src/test/java/org/springframework/integration/security/TestHandler.java similarity index 94% rename from spring-integration-security/src/test/java/org/springframework/integration/security/channel/TestHandler.java rename to spring-integration-security/src/test/java/org/springframework/integration/security/TestHandler.java index 582af2651f..56493fafd5 100644 --- a/spring-integration-security/src/test/java/org/springframework/integration/security/channel/TestHandler.java +++ b/spring-integration-security/src/test/java/org/springframework/integration/security/TestHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.integration.security.channel; +package org.springframework.integration.security; import java.util.ArrayList; import java.util.List; diff --git a/spring-integration-security/src/test/java/org/springframework/integration/security/channel/ChannelAdapterSecurityIntegrationTests-context.xml b/spring-integration-security/src/test/java/org/springframework/integration/security/channel/ChannelAdapterSecurityIntegrationTests-context.xml index 50d3ad0f7d..de11961211 100644 --- a/spring-integration-security/src/test/java/org/springframework/integration/security/channel/ChannelAdapterSecurityIntegrationTests-context.xml +++ b/spring-integration-security/src/test/java/org/springframework/integration/security/channel/ChannelAdapterSecurityIntegrationTests-context.xml @@ -3,18 +3,12 @@ xmlns:si-security="http://www.springframework.org/schema/integration/security" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xmlns:security="http://www.springframework.org/schema/security" - xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd - http://www.springframework.org/schema/security - http://www.springframework.org/schema/security/spring-security.xsd http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd http://www.springframework.org/schema/integration/security - http://www.springframework.org/schema/integration/security/spring-integration-security.xsd - http://www.springframework.org/schema/context - http://www.springframework.org/schema/context/spring-context.xsd"> + http://www.springframework.org/schema/integration/security/spring-integration-security.xsd"> @@ -22,7 +16,7 @@ - + diff --git a/spring-integration-security/src/test/java/org/springframework/integration/security/channel/ChannelAdapterSecurityIntegrationTests.java b/spring-integration-security/src/test/java/org/springframework/integration/security/channel/ChannelAdapterSecurityIntegrationTests.java index 6f8df4a552..eb8d9647a4 100644 --- a/spring-integration-security/src/test/java/org/springframework/integration/security/channel/ChannelAdapterSecurityIntegrationTests.java +++ b/spring-integration-security/src/test/java/org/springframework/integration/security/channel/ChannelAdapterSecurityIntegrationTests.java @@ -23,6 +23,7 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.integration.security.TestHandler; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.support.GenericMessage; import org.springframework.integration.security.SecurityTestUtils; diff --git a/spring-integration-security/src/test/java/org/springframework/integration/security/channel/ChannelSecurityInterceptorBeanPostProcessorTests.java b/spring-integration-security/src/test/java/org/springframework/integration/security/channel/ChannelSecurityInterceptorBeanPostProcessorTests.java index 3438b4ea58..ad683f8c83 100644 --- a/spring-integration-security/src/test/java/org/springframework/integration/security/channel/ChannelSecurityInterceptorBeanPostProcessorTests.java +++ b/spring-integration-security/src/test/java/org/springframework/integration/security/channel/ChannelSecurityInterceptorBeanPostProcessorTests.java @@ -20,14 +20,19 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.util.Collections; +import java.util.Map; import java.util.regex.Pattern; import org.junit.Test; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import org.springframework.aop.support.AopUtils; -import org.springframework.messaging.MessageChannel; +import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.security.config.ChannelSecurityInterceptorBeanPostProcessor; +import org.springframework.messaging.MessageChannel; /** * @author Mark Fisher @@ -35,11 +40,25 @@ import org.springframework.integration.security.config.ChannelSecurityIntercepto public class ChannelSecurityInterceptorBeanPostProcessorTests { @Test - public void securedChannelIsProxied() { + public void securedChannelIsProxied() throws Exception { ChannelSecurityMetadataSource securityMetadataSource = new ChannelSecurityMetadataSource(); securityMetadataSource.addPatternMapping(Pattern.compile("secured.*"), new DefaultChannelAccessPolicy("ROLE_ADMIN", null)); - ChannelSecurityInterceptor interceptor = new ChannelSecurityInterceptor(securityMetadataSource); - ChannelSecurityInterceptorBeanPostProcessor postProcessor = new ChannelSecurityInterceptorBeanPostProcessor(Collections.singletonList(interceptor)); + + final ChannelSecurityInterceptor interceptor = new ChannelSecurityInterceptor(securityMetadataSource); + + ListableBeanFactory beanFactory = Mockito.mock(ListableBeanFactory.class); + Mockito.doAnswer(new Answer>() { + + @Override + public Map answer(InvocationOnMock invocation) throws Throwable { + return Collections.singletonMap("interceptor", interceptor); + } + }).when(beanFactory).getBeansOfType(ChannelSecurityInterceptor.class); + + ChannelSecurityInterceptorBeanPostProcessor postProcessor = new ChannelSecurityInterceptorBeanPostProcessor(); + postProcessor.setBeanFactory(beanFactory); + postProcessor.afterPropertiesSet(); + QueueChannel securedChannel = new QueueChannel(); securedChannel.setBeanName("securedChannel"); MessageChannel postProcessedChannel = (MessageChannel) postProcessor.postProcessAfterInitialization(securedChannel, "securedChannel"); @@ -47,11 +66,24 @@ public class ChannelSecurityInterceptorBeanPostProcessorTests { } @Test - public void nonsecuredChannelIsNotProxied() { + public void nonsecuredChannelIsNotProxied() throws Exception { ChannelSecurityMetadataSource securityMetadataSource = new ChannelSecurityMetadataSource(); securityMetadataSource.addPatternMapping(Pattern.compile("secured.*"), new DefaultChannelAccessPolicy("ROLE_ADMIN", null)); - ChannelSecurityInterceptor interceptor = new ChannelSecurityInterceptor(securityMetadataSource); - ChannelSecurityInterceptorBeanPostProcessor postProcessor = new ChannelSecurityInterceptorBeanPostProcessor(Collections.singletonList(interceptor)); + final ChannelSecurityInterceptor interceptor = new ChannelSecurityInterceptor(securityMetadataSource); + + ListableBeanFactory beanFactory = Mockito.mock(ListableBeanFactory.class); + Mockito.doAnswer(new Answer>() { + + @Override + public Map answer(InvocationOnMock invocation) throws Throwable { + return Collections.singletonMap("interceptor", interceptor); + } + }).when(beanFactory).getBeansOfType(ChannelSecurityInterceptor.class); + + ChannelSecurityInterceptorBeanPostProcessor postProcessor = new ChannelSecurityInterceptorBeanPostProcessor(); + postProcessor.setBeanFactory(beanFactory); + postProcessor.afterPropertiesSet(); + QueueChannel channel = new QueueChannel(); channel.setBeanName("testChannel"); MessageChannel postProcessedChannel = (MessageChannel) postProcessor.postProcessAfterInitialization(channel, "testChannel"); diff --git a/spring-integration-security/src/test/java/org/springframework/integration/security/config/ChannelSecurityInterceptorFactoryBeanTests.java b/spring-integration-security/src/test/java/org/springframework/integration/security/config/ChannelSecurityInterceptorFactoryBeanTests.java new file mode 100644 index 0000000000..96d8f91488 --- /dev/null +++ b/spring-integration-security/src/test/java/org/springframework/integration/security/config/ChannelSecurityInterceptorFactoryBeanTests.java @@ -0,0 +1,167 @@ +/* + * 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 static org.junit.Assert.assertEquals; + +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportResource; +import org.springframework.integration.channel.DirectChannel; +import org.springframework.integration.config.EnableIntegration; +import org.springframework.integration.security.SecurityTestUtils; +import org.springframework.integration.security.TestHandler; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.SubscribableChannel; +import org.springframework.messaging.support.GenericMessage; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Artem Bilan + * @since 4.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +public class ChannelSecurityInterceptorFactoryBeanTests { + + @Autowired + MessageChannel securedChannel; + + @Autowired + MessageChannel securedChannel2; + + @Autowired + MessageChannel unsecuredChannel; + + @Autowired + TestHandler testConsumer; + + + @After + public void tearDown() { + SecurityContextHolder.clearContext(); + } + + + @Test(expected = AccessDeniedException.class) + public void testSecuredWithNotEnoughPermission() { + login("bob", "bobspassword", "ROLE_ADMINA"); + securedChannel.send(new GenericMessage("test")); + } + + @Test + public void testSecuredWithPermission() { + login("bob", "bobspassword", "ROLE_ADMIN", "ROLE_PRESIDENT"); + securedChannel.send(new GenericMessage("test")); + securedChannel2.send(new GenericMessage("test")); + assertEquals("Wrong size of message list in target", 2, testConsumer.sentMessages.size()); + } + + @Test(expected = AccessDeniedException.class) + public void testSecuredWithoutPermision() { + login("bob", "bobspassword", "ROLE_USER"); + securedChannel.send(new GenericMessage("test")); + } + + @Test(expected = AccessDeniedException.class) + public void testSecured2WithoutPermision() { + login("bob", "bobspassword", "ROLE_USER"); + securedChannel2.send(new GenericMessage("test")); + } + + @Test(expected = AuthenticationException.class) + public void testSecuredWithoutAuthenticating() { + securedChannel.send(new GenericMessage("test")); + } + + @Test + public void testUnsecuredAsAdmin() { + login("bob", "bobspassword", "ROLE_ADMIN"); + unsecuredChannel.send(new GenericMessage("test")); + assertEquals("Wrong size of message list in target", 1, testConsumer.sentMessages.size()); + } + + @Test + public void testUnsecuredAsUser() { + login("bob", "bobspassword", "ROLE_USER"); + unsecuredChannel.send(new GenericMessage("test")); + assertEquals("Wrong size of message list in target", 1, testConsumer.sentMessages.size()); + } + + @Test + public void testUnsecuredWithoutAuthenticating() { + unsecuredChannel.send(new GenericMessage("test")); + assertEquals("Wrong size of message list in target", 1, testConsumer.sentMessages.size()); + } + + + private void login(String username, String password, String... roles) { + SecurityContext context = SecurityTestUtils.createContext(username, password, roles); + SecurityContextHolder.setContext(context); + } + + + @Configuration + @EnableIntegration + @ImportResource("classpath:org/springframework/integration/security/config/commonSecurityConfiguration.xml") + public static class ContextConfiguration { + + @Bean + public SubscribableChannel securedChannel() { + return new DirectChannel(); + } + + @Bean + public SubscribableChannel securedChannel2() { + return new DirectChannel(); + } + + @Bean + public SubscribableChannel unsecuredChannel() { + return new DirectChannel(); + } + + @Bean + public TestHandler testHandler() { + TestHandler testHandler = new TestHandler(); + this.securedChannel().subscribe(testHandler); + this.securedChannel2().subscribe(testHandler); + this.unsecuredChannel().subscribe(testHandler); + return testHandler; + } + + @Bean + public ChannelSecurityInterceptorFactoryBean channelSecurityInterceptor() { + return new ChannelSecurityInterceptorFactoryBean() + .accessPolicy("securedChannel.*", "ROLE_ADMIN, ROLE_PRESIDENT"); + } + + } + +} diff --git a/src/reference/docbook/security.xml b/src/reference/docbook/security.xml index 1607658748..28195c041c 100644 --- a/src/reference/docbook/security.xml +++ b/src/reference/docbook/security.xml @@ -58,6 +58,38 @@ + + Starting with version 4.0, the same configuration is available when using + @Configuration classes, by declaring a + ChannelSecurityInterceptorFactoryBean. This class delegates all options for + the ChannelSecurityInterceptor with a builder pattern: + + + + The @EnableIntegration annotation is required to provide the Spring Integration + infrastructure (including Security) to the Application Context. In addition this FactoryBean + falls back to AuthenticationManager and AccessDecisionManager + beans with names authenticationManager and accessDecisionManager respectively, if they aren't provided + in the ChannelSecurityInterceptorFactoryBean bean definition. + diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index b80a8dd1d7..a68cd54b59 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -101,6 +101,14 @@ annotation in a @Configuration class. For more information, see . +
+ ChannelSecurityInterceptorFactoryBean + + Configuration of Spring Security for message channels using @Configuration classes is + now supported by using a ChannelSecurityInterceptorFactoryBean. + For more information, see . + +