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:
Artem Bilan
2014-03-20 11:58:22 +02:00
committed by Gary Russell
parent 5587d79070
commit e3f8ef534b
12 changed files with 425 additions and 34 deletions

View File

@@ -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;

View File

@@ -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"/>

View File

@@ -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;

View File

@@ -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");

View File

@@ -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");
}
}
}