INT-3663: Fix Early BF Access for Security Module
JIRA: https://jira.spring.io/browse/INT-3663 Previously the `ChannelSecurityInterceptorBeanPostProcessor` was populated with direct `BeanDefinition`s for `ChannelSecurityInterceptor`s. It caused an `early access to BeanFactory`. The issue has been introduced by the `ChannelSecurityInterceptorFactoryBean` * Rework `SecurityIntegrationConfigurationInitializer` do not populate `BeanDefinition`s to the `ChannelSecurityInterceptorBeanPostProcessor`, but just `bean names` * Redesign `ChannelSecurityInterceptorBeanPostProcessor` to the `AbstractAutoProxyCreator` * Introduce `SecuredChannel` annotation to be used on the `@Bean` level for `MessageChannel` definition * Move `access policy` mapping to the `SecuredChannel` annotation Address PR comments Document `@SecuredChannel` annotation
This commit is contained in:
committed by
Gary Russell
parent
2653ce9aed
commit
b6cfd4fa76
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2015 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.
|
||||
@@ -30,11 +30,15 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Oleg Zhurakousky
|
||||
* @see SecuredChannel
|
||||
*/
|
||||
public final class ChannelSecurityInterceptor extends AbstractSecurityInterceptor implements MethodInterceptor {
|
||||
|
||||
private final ChannelSecurityMetadataSource securityMetadataSource;
|
||||
|
||||
public ChannelSecurityInterceptor() {
|
||||
this(new ChannelSecurityMetadataSource());
|
||||
}
|
||||
|
||||
public ChannelSecurityInterceptor(ChannelSecurityMetadataSource securityMetadataSource) {
|
||||
Assert.notNull(securityMetadataSource, "securityMetadataSource must not be null");
|
||||
@@ -68,7 +72,6 @@ public final class ChannelSecurityInterceptor extends AbstractSecurityIntercepto
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public SecurityMetadataSource obtainSecurityMetadataSource() {
|
||||
return this.securityMetadataSource;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2015 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,6 +23,7 @@ import java.util.HashSet;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.access.SecurityConfig;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -31,6 +32,7 @@ import org.springframework.util.StringUtils;
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
public class DefaultChannelAccessPolicy implements ChannelAccessPolicy {
|
||||
|
||||
@@ -44,16 +46,14 @@ public class DefaultChannelAccessPolicy implements ChannelAccessPolicy {
|
||||
* values may be a single String or a comma-delimited list of values. All whitespace
|
||||
* will be trimmed. A <code>null</code> value indicates that the policy does not
|
||||
* apply for either send or receive access type. At most one of the values may be null.
|
||||
*
|
||||
* @param sendAccess The send access value(s).
|
||||
* @param receiveAccess The receive access value(s).
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public DefaultChannelAccessPolicy(String sendAccess, String receiveAccess) {
|
||||
boolean sendAccessDefined = StringUtils.hasText(sendAccess);
|
||||
boolean receiveAccessDefined = StringUtils.hasText(receiveAccess);
|
||||
Assert.isTrue(sendAccessDefined || receiveAccessDefined,
|
||||
"At least one of 'sendAccess' and 'receiveAccess' must not be null.");
|
||||
"At least one of 'sendAccess' and 'receiveAccess' must not be null and have at least one entry.");
|
||||
if (sendAccessDefined) {
|
||||
String[] sendAccessValues = StringUtils.commaDelimitedListToStringArray(sendAccess);
|
||||
configAttributeDefinitionForSend = new HashSet<ConfigAttribute>();
|
||||
@@ -62,7 +62,7 @@ public class DefaultChannelAccessPolicy implements ChannelAccessPolicy {
|
||||
}
|
||||
}
|
||||
else {
|
||||
configAttributeDefinitionForSend = Collections.EMPTY_SET;
|
||||
configAttributeDefinitionForSend = Collections.emptySet();
|
||||
}
|
||||
if (receiveAccessDefined) {
|
||||
String[] receiveAccessValues = StringUtils.commaDelimitedListToStringArray(receiveAccess);
|
||||
@@ -72,7 +72,40 @@ public class DefaultChannelAccessPolicy implements ChannelAccessPolicy {
|
||||
}
|
||||
}
|
||||
else {
|
||||
configAttributeDefinitionForReceive = Collections.EMPTY_SET;
|
||||
configAttributeDefinitionForReceive = Collections.emptySet();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an access policy instance. A <code>null</code> value indicates that the policy does not
|
||||
* apply for either send or receive access type. At most one of the values may be null.
|
||||
* Typically is used for the values from the {@link SecuredChannel}
|
||||
* @param sendAccess The send access values.
|
||||
* @param receiveAccess The receive access values.
|
||||
* @since 4.2
|
||||
*/
|
||||
public DefaultChannelAccessPolicy(String[] sendAccess, String[] receiveAccess) {
|
||||
boolean sendAccessDefined = !ObjectUtils.isEmpty(sendAccess);
|
||||
boolean receiveAccessDefined = !ObjectUtils.isEmpty(receiveAccess);
|
||||
Assert.isTrue(sendAccessDefined || receiveAccessDefined,
|
||||
"At least one of 'sendAccess' and 'receiveAccess' must not be null.");
|
||||
if (sendAccessDefined) {
|
||||
configAttributeDefinitionForSend = new HashSet<ConfigAttribute>();
|
||||
for (String sendAccessValue : sendAccess) {
|
||||
configAttributeDefinitionForSend.add(new SecurityConfig(sendAccessValue));
|
||||
}
|
||||
}
|
||||
else {
|
||||
configAttributeDefinitionForSend = Collections.emptySet();
|
||||
}
|
||||
if (receiveAccessDefined) {
|
||||
configAttributeDefinitionForReceive = new HashSet<ConfigAttribute>();
|
||||
for (String receiveAccessValue : receiveAccess) {
|
||||
configAttributeDefinitionForReceive.add(new SecurityConfig(receiveAccessValue));
|
||||
}
|
||||
}
|
||||
else {
|
||||
configAttributeDefinitionForReceive = Collections.emptySet();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2015 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.channel;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* An annotation to be applied for the {@link org.springframework.messaging.MessageChannel} bean definition
|
||||
* from JavaConfig - on {@link @Bean} method level.
|
||||
* <p>
|
||||
* Applies the {@link ChannelSecurityInterceptor}(s) using provided {@link #interceptor()} bean name(s).
|
||||
* <p>
|
||||
* The {@link #sendAccess()} and {@link #receiveAccess()} policies are populated to the
|
||||
* {@link ChannelSecurityInterceptor}(s) from the {@code ChannelSecurityInterceptorBeanPostProcessor}.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @since 4.2
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface SecuredChannel {
|
||||
|
||||
String[] interceptor();
|
||||
|
||||
String[] sendAccess() default {};
|
||||
|
||||
String[] receiveAccess() default {};
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2015 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,16 +16,19 @@
|
||||
|
||||
package org.springframework.integration.security.config;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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.aop.Advisor;
|
||||
import org.springframework.aop.TargetSource;
|
||||
import org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator;
|
||||
import org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.integration.security.channel.ChannelAccessPolicy;
|
||||
import org.springframework.integration.security.channel.ChannelSecurityInterceptor;
|
||||
import org.springframework.integration.security.channel.ChannelSecurityMetadataSource;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
@@ -37,40 +40,61 @@ import org.springframework.messaging.MessageChannel;
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
public class ChannelSecurityInterceptorBeanPostProcessor implements BeanPostProcessor {
|
||||
@SuppressWarnings("serial")
|
||||
public class ChannelSecurityInterceptorBeanPostProcessor extends AbstractAutoProxyCreator {
|
||||
|
||||
private final Collection<ChannelSecurityInterceptor> securityInterceptors;
|
||||
private final Map<String, Set<Pattern>> securityInterceptorMappings;
|
||||
|
||||
public ChannelSecurityInterceptorBeanPostProcessor(Collection<ChannelSecurityInterceptor> securityInterceptors) {
|
||||
this.securityInterceptors = securityInterceptors;
|
||||
private final Map<String, Map<Pattern, ChannelAccessPolicy>> accessPolicyMapping;
|
||||
|
||||
public ChannelSecurityInterceptorBeanPostProcessor(Map<String, Set<Pattern>> securityInterceptorMappings) {
|
||||
this(securityInterceptorMappings, null);
|
||||
}
|
||||
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
return bean;
|
||||
public ChannelSecurityInterceptorBeanPostProcessor(Map<String, Set<Pattern>> securityInterceptorMappings,
|
||||
Map<String, Map<Pattern, ChannelAccessPolicy>> accessPolicyMapping) {
|
||||
this.securityInterceptorMappings = securityInterceptorMappings;//NOSONAR (inconsistent sync)
|
||||
this.accessPolicyMapping = accessPolicyMapping;//NOSONAR (inconsistent sync)
|
||||
}
|
||||
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (bean instanceof MessageChannel) {
|
||||
for (ChannelSecurityInterceptor securityInterceptor : this.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();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) {
|
||||
if (this.accessPolicyMapping != null
|
||||
&& bean instanceof ChannelSecurityInterceptor
|
||||
&& accessPolicyMapping.containsKey(beanName)) {
|
||||
Map<Pattern, ChannelAccessPolicy> accessPolicies = this.accessPolicyMapping.get(beanName);
|
||||
ChannelSecurityMetadataSource securityMetadataSource =
|
||||
(ChannelSecurityMetadataSource) ((ChannelSecurityInterceptor) bean).obtainSecurityMetadataSource();
|
||||
for (Map.Entry<Pattern, ChannelAccessPolicy> entry : accessPolicies.entrySet()) {
|
||||
securityMetadataSource.addPatternMapping(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
private boolean shouldProxy(String beanName, ChannelSecurityMetadataSource channelSecurityMetadataSource) {
|
||||
Set<Pattern> patterns = channelSecurityMetadataSource.getPatterns();
|
||||
@Override
|
||||
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName,
|
||||
TargetSource customTargetSource) throws BeansException {
|
||||
if (MessageChannel.class.isAssignableFrom(beanClass)) {
|
||||
List<Advisor> interceptors = new ArrayList<Advisor>();
|
||||
for (Map.Entry<String, Set<Pattern>> entry : this.securityInterceptorMappings.entrySet()) {
|
||||
if (isMatch(beanName, entry.getValue())) {
|
||||
DefaultBeanFactoryPointcutAdvisor channelSecurityInterceptor
|
||||
= new DefaultBeanFactoryPointcutAdvisor();
|
||||
channelSecurityInterceptor.setAdviceBeanName(entry.getKey());
|
||||
channelSecurityInterceptor.setBeanFactory(getBeanFactory());
|
||||
interceptors.add(channelSecurityInterceptor);
|
||||
}
|
||||
}
|
||||
if (!interceptors.isEmpty()) {
|
||||
return interceptors.toArray();
|
||||
}
|
||||
}
|
||||
|
||||
return DO_NOT_PROXY;
|
||||
}
|
||||
|
||||
private boolean isMatch(String beanName, Set<Pattern> patterns) {
|
||||
for (Pattern pattern : patterns) {
|
||||
if (pattern.matcher(beanName).matches()) {
|
||||
return true;
|
||||
|
||||
@@ -40,7 +40,10 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @since 4.0
|
||||
* @deprecated in favor of direct {@link ChannelSecurityInterceptor} usage and
|
||||
* {@link org.springframework.integration.security.channel.SecuredChannel} annotation.
|
||||
*/
|
||||
@Deprecated
|
||||
public class ChannelSecurityInterceptorFactoryBean implements FactoryBean<ChannelSecurityInterceptor>, BeanNameAware, BeanFactoryAware {
|
||||
|
||||
private final ChannelSecurityInterceptor interceptor = new ChannelSecurityInterceptor(new ChannelSecurityMetadataSource());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
* Copyright 2014-2015 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,23 +16,24 @@
|
||||
|
||||
package org.springframework.integration.security.config;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.CannotLoadBeanClassException;
|
||||
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.ManagedList;
|
||||
import org.springframework.beans.factory.support.ManagedMap;
|
||||
import org.springframework.beans.factory.support.ManagedSet;
|
||||
import org.springframework.core.type.MethodMetadata;
|
||||
import org.springframework.core.type.StandardMethodMetadata;
|
||||
import org.springframework.integration.config.IntegrationConfigurationInitializer;
|
||||
import org.springframework.integration.security.channel.ChannelAccessPolicy;
|
||||
import org.springframework.integration.security.channel.ChannelSecurityInterceptor;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.integration.security.channel.DefaultChannelAccessPolicy;
|
||||
import org.springframework.integration.security.channel.SecuredChannel;
|
||||
|
||||
/**
|
||||
* The Integration Security infrastructure {@code beanFactory} initializer.
|
||||
@@ -46,45 +47,75 @@ public class SecurityIntegrationConfigurationInitializer implements IntegrationC
|
||||
ChannelSecurityInterceptorBeanPostProcessor.class.getName();
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void initialize(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
|
||||
|
||||
List<BeanDefinition> securityInterceptors = new ManagedList<BeanDefinition>();
|
||||
Map<String, ManagedSet<String>> securityInterceptors = new ManagedMap<String, ManagedSet<String>>();
|
||||
Map<String, Map<Pattern, ChannelAccessPolicy>> policies = new HashMap<String, Map<Pattern, ChannelAccessPolicy>>();
|
||||
|
||||
for (String beanName : registry.getBeanDefinitionNames()) {
|
||||
BeanDefinition beanDefinition = registry.getBeanDefinition(beanName);
|
||||
String beanClassName = beanDefinition.getBeanClassName();
|
||||
Class<?> clazz = null;
|
||||
if (StringUtils.hasText(beanClassName)) {
|
||||
try {
|
||||
clazz = ClassUtils.forName(beanClassName, beanFactory.getBeanClassLoader());
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
throw new CannotLoadBeanClassException(this.toString(), beanName, beanClassName, e);
|
||||
}
|
||||
}
|
||||
else if (beanDefinition instanceof AnnotatedBeanDefinition
|
||||
&& beanDefinition.getSource() instanceof MethodMetadata) {
|
||||
MethodMetadata beanMethod = (MethodMetadata) beanDefinition.getSource();
|
||||
if (beanMethod instanceof StandardMethodMetadata) {
|
||||
Method method = ((StandardMethodMetadata) beanMethod).getIntrospectedMethod();
|
||||
clazz = method.getReturnType();
|
||||
}
|
||||
}
|
||||
if (ChannelSecurityInterceptor.class.getName().equals(beanDefinition.getBeanClassName())) {
|
||||
BeanDefinition metadataSource = (BeanDefinition) beanDefinition.getConstructorArgumentValues()
|
||||
.getIndexedArgumentValue(0, BeanDefinition.class)
|
||||
.getValue();
|
||||
|
||||
if (clazz != null &&
|
||||
(ChannelSecurityInterceptor.class.isAssignableFrom(clazz)
|
||||
|| ChannelSecurityInterceptorFactoryBean.class.isAssignableFrom(clazz))) {
|
||||
securityInterceptors.add(beanDefinition);
|
||||
Map<String, ?> value = (Map<String, ?>) metadataSource.getConstructorArgumentValues()
|
||||
.getIndexedArgumentValue(0, Map.class)
|
||||
.getValue();
|
||||
ManagedSet<String> patterns = new ManagedSet<String>();
|
||||
if (!securityInterceptors.containsKey(beanName)) {
|
||||
securityInterceptors.put(beanName, patterns);
|
||||
}
|
||||
else {
|
||||
patterns = securityInterceptors.get(beanName);
|
||||
}
|
||||
patterns.addAll(value.keySet());
|
||||
}
|
||||
else if (beanDefinition instanceof AnnotatedBeanDefinition) {
|
||||
if (beanDefinition.getSource() instanceof MethodMetadata) {
|
||||
MethodMetadata beanMethod = (MethodMetadata) beanDefinition.getSource();
|
||||
String annotationType = SecuredChannel.class.getName();
|
||||
if (beanMethod.isAnnotated(annotationType)) {
|
||||
Map<String, Object> securedAttributes = beanMethod.getAnnotationAttributes(annotationType);
|
||||
String[] interceptors = (String[]) securedAttributes.get("interceptor");
|
||||
String[] sendAccess = (String[]) securedAttributes.get("sendAccess");
|
||||
String[] receiveAccess = (String[]) securedAttributes.get("receiveAccess");
|
||||
ChannelAccessPolicy accessPolicy = new DefaultChannelAccessPolicy(sendAccess, receiveAccess);
|
||||
for (String interceptor : interceptors) {
|
||||
ManagedSet<String> patterns = new ManagedSet<String>();
|
||||
if (!securityInterceptors.containsKey(interceptor)) {
|
||||
securityInterceptors.put(interceptor, patterns);
|
||||
}
|
||||
else {
|
||||
patterns = securityInterceptors.get(interceptor);
|
||||
}
|
||||
patterns.add(beanName);
|
||||
|
||||
Map<Pattern, ChannelAccessPolicy> mapping = new HashMap<Pattern, ChannelAccessPolicy>();
|
||||
if (!policies.containsKey(interceptor)) {
|
||||
policies.put(interceptor, mapping);
|
||||
}
|
||||
else {
|
||||
mapping = policies.get(interceptor);
|
||||
}
|
||||
mapping.put(Pattern.compile(beanName), accessPolicy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!securityInterceptors.isEmpty()) {
|
||||
BeanDefinition securityPostProcessorBd =
|
||||
|
||||
BeanDefinitionBuilder builder =
|
||||
BeanDefinitionBuilder.rootBeanDefinition(ChannelSecurityInterceptorBeanPostProcessor.class)
|
||||
.addConstructorArgValue(securityInterceptors)
|
||||
.getBeanDefinition();
|
||||
registry.registerBeanDefinition(CHANNEL_SECURITY_INTERCEPTOR_BPP_BEAN_NAME, securityPostProcessorBd);
|
||||
.addConstructorArgValue(securityInterceptors);
|
||||
if (!policies.isEmpty()) {
|
||||
builder.addConstructorArgValue(policies);
|
||||
}
|
||||
registry.registerBeanDefinition(CHANNEL_SECURITY_INTERCEPTOR_BPP_BEAN_NAME, builder.getBeanDefinition());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.channel;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.security.config.ChannelSecurityInterceptorBeanPostProcessor;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
public class ChannelSecurityInterceptorBeanPostProcessorTests {
|
||||
|
||||
@Test
|
||||
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(Arrays.asList(interceptor));
|
||||
|
||||
QueueChannel securedChannel = new QueueChannel();
|
||||
securedChannel.setBeanName("securedChannel");
|
||||
MessageChannel postProcessedChannel =
|
||||
(MessageChannel) postProcessor.postProcessAfterInitialization(securedChannel, "securedChannel");
|
||||
assertTrue(AopUtils.isAopProxy(postProcessedChannel));
|
||||
}
|
||||
|
||||
@Test
|
||||
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(Arrays.asList(interceptor));
|
||||
|
||||
QueueChannel channel = new QueueChannel();
|
||||
channel.setBeanName("testChannel");
|
||||
MessageChannel postProcessedChannel =
|
||||
(MessageChannel) postProcessor.postProcessAfterInitialization(channel, "testChannel");
|
||||
assertFalse(AopUtils.isAopProxy(postProcessedChannel));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
* Copyright 2014-2015 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.
|
||||
@@ -30,10 +30,14 @@ 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.integration.security.channel.ChannelSecurityInterceptor;
|
||||
import org.springframework.integration.security.channel.SecuredChannel;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.security.access.AccessDecisionManager;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
@@ -48,7 +52,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
|
||||
public class ChannelSecurityInterceptorFactoryBeanTests {
|
||||
public class ChannelSecurityInterceptorSecuredChannelAnnotationTests {
|
||||
|
||||
@Autowired
|
||||
MessageChannel securedChannel;
|
||||
@@ -133,11 +137,13 @@ public class ChannelSecurityInterceptorFactoryBeanTests {
|
||||
public static class ContextConfiguration {
|
||||
|
||||
@Bean
|
||||
@SecuredChannel(interceptor = "channelSecurityInterceptor", sendAccess = {"ROLE_ADMIN", "ROLE_PRESIDENT"})
|
||||
public SubscribableChannel securedChannel() {
|
||||
return new DirectChannel();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@SecuredChannel(interceptor = "channelSecurityInterceptor", sendAccess = {"ROLE_ADMIN", "ROLE_PRESIDENT"})
|
||||
public SubscribableChannel securedChannel2() {
|
||||
return new DirectChannel();
|
||||
}
|
||||
@@ -157,9 +163,12 @@ public class ChannelSecurityInterceptorFactoryBeanTests {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ChannelSecurityInterceptorFactoryBean channelSecurityInterceptor() {
|
||||
return new ChannelSecurityInterceptorFactoryBean()
|
||||
.accessPolicy("securedChannel.*", "ROLE_ADMIN, ROLE_PRESIDENT");
|
||||
public ChannelSecurityInterceptor channelSecurityInterceptor(AuthenticationManager authenticationManager,
|
||||
AccessDecisionManager accessDecisionManager) {
|
||||
ChannelSecurityInterceptor channelSecurityInterceptor = new ChannelSecurityInterceptor();
|
||||
channelSecurityInterceptor.setAuthenticationManager(authenticationManager);
|
||||
channelSecurityInterceptor.setAccessDecisionManager(accessDecisionManager);
|
||||
return channelSecurityInterceptor;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -55,8 +55,14 @@ Where this is not the case references to the appropriate beans can be configured
|
||||
|
||||
----
|
||||
|
||||
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:
|
||||
Starting with _version 4.2_, the `@SecuredChannel` annotation is available, replacing the deprecated
|
||||
`ChannelSecurityInterceptorFactoryBean`, which was introduced in _version 4.0_ for Java & Annotation
|
||||
configuration in `@Configuration` classes.
|
||||
The `ChannelSecurityInterceptorFactoryBean` has been deprecated to
|
||||
avoid the possibility of undesired early load for dependent beans from the `BeanFactory` during the `ApplicationContext` initialization
|
||||
phase.
|
||||
|
||||
With the `@SecuredChannel` annotation, the Java configuration variant of the XML configuration above is:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -64,22 +70,26 @@ This class delegates all options for the `ChannelSecurityInterceptor` with a _bu
|
||||
@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");
|
||||
}
|
||||
@SecuredChannel(interceptor = "channelSecurityInterceptor", sendAccess = "ROLE_ADMIN")
|
||||
public SubscribableChannel adminChannel() {
|
||||
return new DirectChannel();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@SecuredChannel(interceptor = "channelSecurityInterceptor", receiveAccess = "ROLE_USER")
|
||||
public SubscribableChannel userChannel() {
|
||||
return new DirectChannel();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ChannelSecurityInterceptor channelSecurityInterceptor(AuthenticationManager authenticationManager,
|
||||
AccessDecisionManager accessDecisionManager) {
|
||||
ChannelSecurityInterceptor channelSecurityInterceptor = new ChannelSecurityInterceptor();
|
||||
channelSecurityInterceptor.setAuthenticationManager(authenticationManager);
|
||||
channelSecurityInterceptor.setAccessDecisionManager(accessDecisionManager);
|
||||
return channelSecurityInterceptor;
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: 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.
|
||||
|
||||
@@ -22,6 +22,13 @@ For complete details, see <<jmx-42-improvements>>.
|
||||
|
||||
The `MongoDbMetadataStore` is now available. For more information, see <<mongodb-metadata-store>>.
|
||||
|
||||
[[x4.2-secured-channel-annotation]]
|
||||
==== SecuredChannel Annotation
|
||||
|
||||
The `@SecuredChannel` annotation has been introduced, replacing the deprecated `ChannelSecurityInterceptorFactoryBean`.
|
||||
For more information, see <<security>>.
|
||||
|
||||
|
||||
[[x4.2-general]]
|
||||
=== General Changes
|
||||
|
||||
|
||||
Reference in New Issue
Block a user