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
This commit is contained in:
committed by
Gary Russell
parent
5587d79070
commit
e3f8ef534b
@@ -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;
|
||||
|
||||
|
||||
@@ -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<ChannelSecurityInterceptor> securityInterceptors;
|
||||
private volatile Collection<ChannelSecurityInterceptor> securityInterceptors;
|
||||
|
||||
private ListableBeanFactory beanFactory;
|
||||
|
||||
public ChannelSecurityInterceptorBeanPostProcessor(Collection<ChannelSecurityInterceptor> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 <security:secured-channels/>} JavaConfig variant to provide options
|
||||
* for {@link ChannelSecurityInterceptor} beans.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @since 4.0
|
||||
*/
|
||||
public class ChannelSecurityInterceptorFactoryBean implements FactoryBean<ChannelSecurityInterceptor>, 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<String, DefaultChannelAccessPolicy> accessPolicies) {
|
||||
Assert.notNull(accessPolicies);
|
||||
ChannelSecurityMetadataSource channelSecurityMetadataSource = (ChannelSecurityMetadataSource) interceptor.obtainSecurityMetadataSource();
|
||||
for (Map.Entry<String, DefaultChannelAccessPolicy> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Element> accessPolicyElements = DomUtils.getChildElementsByTagName(element, "access-policy");
|
||||
ManagedMap<Pattern, BeanDefinition> patternMappings = new ManagedMap<Pattern, BeanDefinition>();
|
||||
ManagedMap<String, BeanDefinition> patternMappings = new ManagedMap<String, BeanDefinition>();
|
||||
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);
|
||||
|
||||
|
||||
@@ -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<ChannelSecurityInterceptor> 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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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">
|
||||
|
||||
<beans:import resource="classpath:org/springframework/integration/security/config/commonSecurityConfiguration.xml"/>
|
||||
|
||||
@@ -22,7 +16,7 @@
|
||||
<si-security:access-policy pattern="securedChannel.*" send-access="ROLE_ADMIN, ROLE_PRESIDENT"/>
|
||||
</si-security:secured-channels>
|
||||
|
||||
<beans:bean id="testHandler" class="org.springframework.integration.security.channel.TestHandler"/>
|
||||
<beans:bean id="testHandler" class="org.springframework.integration.security.TestHandler"/>
|
||||
|
||||
<outbound-channel-adapter id="securedChannelAdapter" ref="testHandler"/>
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Map<String, ChannelSecurityInterceptor>>() {
|
||||
|
||||
@Override
|
||||
public Map<String, ChannelSecurityInterceptor> 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<Map<String, ChannelSecurityInterceptor>>() {
|
||||
|
||||
@Override
|
||||
public Map<String, ChannelSecurityInterceptor> 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");
|
||||
|
||||
@@ -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<String>("test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecuredWithPermission() {
|
||||
login("bob", "bobspassword", "ROLE_ADMIN", "ROLE_PRESIDENT");
|
||||
securedChannel.send(new GenericMessage<String>("test"));
|
||||
securedChannel2.send(new GenericMessage<String>("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<String>("test"));
|
||||
}
|
||||
|
||||
@Test(expected = AccessDeniedException.class)
|
||||
public void testSecured2WithoutPermision() {
|
||||
login("bob", "bobspassword", "ROLE_USER");
|
||||
securedChannel2.send(new GenericMessage<String>("test"));
|
||||
}
|
||||
|
||||
@Test(expected = AuthenticationException.class)
|
||||
public void testSecuredWithoutAuthenticating() {
|
||||
securedChannel.send(new GenericMessage<String>("test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsecuredAsAdmin() {
|
||||
login("bob", "bobspassword", "ROLE_ADMIN");
|
||||
unsecuredChannel.send(new GenericMessage<String>("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<String>("test"));
|
||||
assertEquals("Wrong size of message list in target", 1, testConsumer.sentMessages.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsecuredWithoutAuthenticating() {
|
||||
unsecuredChannel.send(new GenericMessage<String>("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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -58,6 +58,38 @@
|
||||
</programlisting>
|
||||
|
||||
</para>
|
||||
<para>
|
||||
Starting with <emphasis>version 4.0</emphasis>, the same configuration is available when using
|
||||
<code>@Configuration</code> classes, by declaring a
|
||||
<classname>ChannelSecurityInterceptorFactoryBean</classname>. This class delegates all options for
|
||||
the <classname>ChannelSecurityInterceptor</classname> with a <emphasis>builder</emphasis> pattern:
|
||||
</para>
|
||||
<programlisting language="java"><![CDATA[@Configuration
|
||||
@EnableIntegration
|
||||
public class ContextConfiguration {
|
||||
|
||||
@Autowired
|
||||
private AuthenticationManager authenticationManager;
|
||||
|
||||
@Autowired
|
||||
private AccessDecisionManager accessDecisionManager;
|
||||
|
||||
@Bean
|
||||
public ChannelSecurityInterceptorFactoryBean channelSecurityInterceptor() {
|
||||
return new ChannelSecurityInterceptorFactoryBean()
|
||||
.authenticationManager(this.authenticationManager)
|
||||
.accessDecisionManager(this.accessDecisionManager)
|
||||
.accessPolicy("admin.*", "ROLE_ADMIN")
|
||||
.accessPolicy("user.*", null, "ROLE_USER");
|
||||
}
|
||||
}]]></programlisting>
|
||||
<note>
|
||||
The <classname>@EnableIntegration</classname> annotation is required to provide the Spring Integration
|
||||
infrastructure (including Security) to the Application Context. In addition this <interfacename>FactoryBean</interfacename>
|
||||
falls back to <interfacename>AuthenticationManager</interfacename> and <interfacename>AccessDecisionManager</interfacename>
|
||||
beans with names <code>authenticationManager</code> and <code>accessDecisionManager</code> respectively, if they aren't provided
|
||||
in the <classname>ChannelSecurityInterceptorFactoryBean</classname> bean definition.
|
||||
</note>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
@@ -101,6 +101,14 @@
|
||||
annotation in a <code>@Configuration</code> class. For more information, see <xref linkend="jmx-mbean-exporter"/>.
|
||||
</para>
|
||||
</section>
|
||||
<section id="4.0-channel-security-interceptor">
|
||||
<title>ChannelSecurityInterceptorFactoryBean</title>
|
||||
<para>
|
||||
Configuration of Spring Security for message channels using <code>@Configuration</code> classes is
|
||||
now supported by using a <classname>ChannelSecurityInterceptorFactoryBean</classname>.
|
||||
For more information, see <xref linkend="security"/>.
|
||||
</para>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section id="4.0-general">
|
||||
|
||||
Reference in New Issue
Block a user