Deprecate spring-integration-security module
The `SecurityContextPropagationChannelInterceptor` has been migrated to `spring-security-messaging`. Since it was only the class in the `spring-integration-security`, it is now fully considered as deprecated * Remove all the tests from `spring-integration-security` * Modify `HttpDslTests` to demonstrate the `spring-security-messaging` in action which has been replaced with whatever there was in `spring-integration-security` * Remove redundant `exclude group: 'org.springframework'` for security dependencies in `build.gradle` since all of them rely on the same SF deps as SI
This commit is contained in:
26
build.gradle
26
build.gradle
@@ -702,14 +702,10 @@ project('spring-integration-http') {
|
||||
optionalApi "com.rometools:rome:$romeToolsVersion"
|
||||
optionalApi 'org.springframework:spring-webflux'
|
||||
|
||||
testImplementation project(':spring-integration-security')
|
||||
testImplementation "org.hamcrest:hamcrest-core:$hamcrestVersion"
|
||||
testImplementation('org.springframework.security:spring-security-config') {
|
||||
exclude group: 'org.springframework'
|
||||
}
|
||||
testImplementation('org.springframework.security:spring-security-test') {
|
||||
exclude group: 'org.springframework'
|
||||
}
|
||||
testImplementation 'org.springframework.security:spring-security-messaging'
|
||||
testImplementation 'org.springframework.security:spring-security-config'
|
||||
testImplementation 'org.springframework.security:spring-security-test'
|
||||
testImplementation 'com.fasterxml.jackson.core:jackson-databind'
|
||||
|
||||
testRuntimeOnly "com.jayway.jsonpath:json-path:$jsonpathVersion"
|
||||
@@ -916,13 +912,9 @@ project('spring-integration-security') {
|
||||
description = 'Spring Integration Security Support'
|
||||
dependencies {
|
||||
api project(':spring-integration-core')
|
||||
api('org.springframework.security:spring-security-messaging') {
|
||||
exclude group: 'org.springframework'
|
||||
}
|
||||
api 'org.springframework.security:spring-security-messaging'
|
||||
|
||||
testImplementation('org.springframework.security:spring-security-config') {
|
||||
exclude group: 'org.springframework'
|
||||
}
|
||||
testImplementation 'org.springframework.security:spring-security-config'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1017,12 +1009,8 @@ project('spring-integration-webflux') {
|
||||
testImplementation "jakarta.servlet:jakarta.servlet-api:$servletApiVersion"
|
||||
testImplementation "org.hamcrest:hamcrest-core:$hamcrestVersion"
|
||||
testImplementation 'org.springframework:spring-webmvc'
|
||||
testImplementation('org.springframework.security:spring-security-config') {
|
||||
exclude group: 'org.springframework'
|
||||
}
|
||||
testImplementation('org.springframework.security:spring-security-test') {
|
||||
exclude group: 'org.springframework'
|
||||
}
|
||||
testImplementation 'org.springframework.security:spring-security-config'
|
||||
testImplementation 'org.springframework.security:spring-security-test'
|
||||
testImplementation 'com.fasterxml.jackson.core:jackson-databind'
|
||||
testImplementation 'io.micrometer:micrometer-observation-test'
|
||||
testImplementation('io.micrometer:micrometer-tracing-integration-test') {
|
||||
|
||||
@@ -36,14 +36,18 @@ import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.FixedSubscriberChannel;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.config.EnableIntegration;
|
||||
import org.springframework.integration.dsl.DirectChannelSpec;
|
||||
import org.springframework.integration.dsl.IntegrationFlow;
|
||||
import org.springframework.integration.dsl.MessageChannels;
|
||||
import org.springframework.integration.dsl.QueueChannelSpec;
|
||||
import org.springframework.integration.dsl.context.IntegrationFlowContext;
|
||||
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
|
||||
import org.springframework.integration.http.multipart.UploadedMultipartFile;
|
||||
import org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler;
|
||||
import org.springframework.integration.scheduling.PollerMetadata;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.messaging.support.ErrorMessage;
|
||||
import org.springframework.mock.web.MockPart;
|
||||
@@ -56,6 +60,7 @@ import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
||||
import org.springframework.security.messaging.access.intercept.AuthorizationChannelInterceptor;
|
||||
import org.springframework.security.messaging.context.SecurityContextPropagationChannelInterceptor;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
@@ -330,21 +335,33 @@ public class HttpDslTests {
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessageChannel transformSecuredChannel() {
|
||||
DirectChannel directChannel = new DirectChannel();
|
||||
directChannel.addInterceptor(
|
||||
new AuthorizationChannelInterceptor(AuthorityAuthorizationManager.hasRole("ADMIN")));
|
||||
return directChannel;
|
||||
@Bean(PollerMetadata.DEFAULT_POLLER)
|
||||
PollerMetadata pollerMetadata() {
|
||||
return new PollerMetadata();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public IntegrationFlow httpInternalServiceFlow() {
|
||||
public QueueChannelSpec securityPropagationChannel() {
|
||||
return MessageChannels.queue()
|
||||
.interceptor(new SecurityContextPropagationChannelInterceptor());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DirectChannelSpec transformSecuredChannel() {
|
||||
return MessageChannels.direct()
|
||||
.interceptor(new AuthorizationChannelInterceptor(AuthorityAuthorizationManager.hasRole("ADMIN")));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public IntegrationFlow httpInternalServiceFlow(QueueChannel securityPropagationChannel,
|
||||
DirectChannel transformSecuredChannel) {
|
||||
|
||||
return IntegrationFlow
|
||||
.from(Http.inboundGateway("/service/internal")
|
||||
.requestMapping(r -> r.params("name"))
|
||||
.payloadExpression("#requestParams.name"))
|
||||
.channel(transformSecuredChannel())
|
||||
.channel(securityPropagationChannel)
|
||||
.channel(transformSecuredChannel)
|
||||
.<List<String>, String>transform(p -> p.get(0).toUpperCase())
|
||||
.get();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2019 the original author or authors.
|
||||
* Copyright 2015-2023 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.
|
||||
@@ -43,7 +43,10 @@ import org.springframework.security.core.context.SecurityContextHolder;
|
||||
* @since 4.2
|
||||
*
|
||||
* @see ThreadStatePropagationChannelInterceptor
|
||||
*
|
||||
* @deprecated since 6.2 in favor of {@link org.springframework.security.messaging.context.SecurityContextPropagationChannelInterceptor}
|
||||
*/
|
||||
@Deprecated(since = "6.2", forRemoval = true)
|
||||
public class SecurityContextPropagationChannelInterceptor
|
||||
extends ThreadStatePropagationChannelInterceptor<Authentication> {
|
||||
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2019 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
|
||||
*
|
||||
* https://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;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextImpl;
|
||||
|
||||
/**
|
||||
* @author Jonas Partner
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public class SecurityTestUtils {
|
||||
|
||||
private SecurityTestUtils() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static SecurityContext createContext(String username, String password, String... roles) {
|
||||
SecurityContextImpl ctxImpl = new SecurityContextImpl();
|
||||
UsernamePasswordAuthenticationToken authToken;
|
||||
if (roles != null && roles.length > 0) {
|
||||
GrantedAuthority[] authorities = new GrantedAuthority[roles.length];
|
||||
for (int i = 0; i < roles.length; i++) {
|
||||
authorities[i] = new SimpleGrantedAuthority(roles[i]);
|
||||
}
|
||||
authToken = new UsernamePasswordAuthenticationToken(username, password,
|
||||
Arrays.asList(authorities));
|
||||
}
|
||||
else {
|
||||
authToken = new UsernamePasswordAuthenticationToken(username, password);
|
||||
}
|
||||
ctxImpl.setAuthentication(authToken);
|
||||
return ctxImpl;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2019 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
|
||||
*
|
||||
* https://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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class TestHandler implements MessageHandler {
|
||||
|
||||
public List<Message<?>> sentMessages = new ArrayList<Message<?>>();
|
||||
|
||||
public void handleMessage(Message<?> message) {
|
||||
sentMessages.add(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans:beans xmlns="http://www.springframework.org/schema/integration"
|
||||
xmlns:beans="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:task="http://www.springframework.org/schema/task"
|
||||
xmlns:security="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration
|
||||
https://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/task https://www.springframework.org/schema/task/spring-task.xsd">
|
||||
|
||||
<security:user-service id="userDetailsService">
|
||||
<security:user name="jimi" password="jimispassword" authorities="ROLE_USER, ROLE_ADMIN"/>
|
||||
<security:user name="bob" password="bobspassword" authorities="ROLE_USER"/>
|
||||
</security:user-service>
|
||||
|
||||
<channel-interceptor pattern="securedChannel*">
|
||||
<beans:bean
|
||||
class="org.springframework.security.messaging.access.intercept.AuthorizationChannelInterceptor">
|
||||
<beans:constructor-arg>
|
||||
<beans:bean class="org.springframework.security.authorization.AuthorityAuthorizationManager"
|
||||
factory-method="hasAnyRole">
|
||||
<beans:constructor-arg>
|
||||
<beans:array>
|
||||
<beans:value>ADMIN</beans:value>
|
||||
<beans:value>PRESIDENT</beans:value>
|
||||
</beans:array>
|
||||
</beans:constructor-arg>
|
||||
</beans:bean>
|
||||
</beans:constructor-arg>
|
||||
</beans:bean>
|
||||
</channel-interceptor>
|
||||
|
||||
<beans:bean id="testHandler" class="org.springframework.integration.security.TestHandler"/>
|
||||
|
||||
<outbound-channel-adapter id="securedChannelAdapter" ref="testHandler"/>
|
||||
|
||||
<service-activator input-channel="securedChannelAdapter2" ref="testHandler"/>
|
||||
|
||||
<outbound-channel-adapter id="unsecuredChannelAdapter" ref="testHandler"/>
|
||||
|
||||
<channel id="queueChannel">
|
||||
<queue/>
|
||||
<interceptors>
|
||||
<beans:bean
|
||||
class="org.springframework.integration.security.channel.SecurityContextPropagationChannelInterceptor"/>
|
||||
</interceptors>
|
||||
</channel>
|
||||
|
||||
<!--The single taskScheduler Thread is need to check that SecurityContext cleanup works well-->
|
||||
<task:scheduler id="taskScheduler"/>
|
||||
|
||||
<bridge input-channel="queueChannel" output-channel="securedChannelQueue">
|
||||
<poller fixed-delay="100"/>
|
||||
</bridge>
|
||||
|
||||
<channel id="securedChannelQueue">
|
||||
<queue/>
|
||||
</channel>
|
||||
|
||||
<channel id="errorChannel">
|
||||
<queue/>
|
||||
</channel>
|
||||
|
||||
</beans:beans>
|
||||
@@ -1,167 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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
|
||||
*
|
||||
* https://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 org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.integration.security.SecurityTestUtils;
|
||||
import org.springframework.integration.security.TestHandler;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageDeliveryException;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
|
||||
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.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@SpringJUnitConfig
|
||||
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
|
||||
public class ChannelAdapterSecurityIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("securedChannelAdapter")
|
||||
MessageChannel securedChannelAdapter;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("securedChannelAdapter")
|
||||
MessageChannel securedChannelAdapter2;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("unsecuredChannelAdapter")
|
||||
MessageChannel unsecuredChannelAdapter;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("queueChannel")
|
||||
MessageChannel queueChannel;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("securedChannelQueue")
|
||||
PollableChannel securedChannelQueue;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("errorChannel")
|
||||
PollableChannel errorChannel;
|
||||
|
||||
@Autowired
|
||||
TestHandler testConsumer;
|
||||
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSecuredWithNotEnoughPermission() {
|
||||
login("bob", "bobspassword", "ROLE_ADMINA");
|
||||
assertThatExceptionOfType(MessageDeliveryException.class)
|
||||
.isThrownBy(() -> this.securedChannelAdapter.send(new GenericMessage<>("test")))
|
||||
.withRootCauseExactlyInstanceOf(AccessDeniedException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecuredWithPermission() {
|
||||
login("bob", "bobspassword", "ROLE_ADMIN", "ROLE_PRESIDENT");
|
||||
securedChannelAdapter.send(new GenericMessage<>("test"));
|
||||
securedChannelAdapter2.send(new GenericMessage<>("test"));
|
||||
assertThat(testConsumer.sentMessages.size()).as("Wrong size of message list in target").isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecurityContextPropagation() {
|
||||
login("bob", "bobspassword", "ROLE_ADMIN", "ROLE_PRESIDENT");
|
||||
this.queueChannel.send(new GenericMessage<>("test"));
|
||||
Message<?> receive = this.securedChannelQueue.receive(10000);
|
||||
assertThat(receive).isNotNull();
|
||||
|
||||
SecurityContextHolder.clearContext();
|
||||
|
||||
this.queueChannel.send(new GenericMessage<>("test"));
|
||||
Message<?> errorMessage = this.errorChannel.receive(10000);
|
||||
assertThat(errorMessage).isNotNull();
|
||||
Object payload = errorMessage.getPayload();
|
||||
assertThat(payload).isInstanceOf(MessageDeliveryException.class);
|
||||
assertThat(((MessageDeliveryException) payload).getCause())
|
||||
.isInstanceOf(AuthenticationCredentialsNotFoundException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecuredWithoutPermission() {
|
||||
login("bob", "bobspassword", "ROLE_USER");
|
||||
assertThatExceptionOfType(MessageDeliveryException.class)
|
||||
.isThrownBy(() -> this.securedChannelAdapter.send(new GenericMessage<>("test")))
|
||||
.withRootCauseExactlyInstanceOf(AccessDeniedException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecured2WithoutPermission() {
|
||||
login("bob", "bobspassword", "ROLE_USER");
|
||||
assertThatExceptionOfType(MessageDeliveryException.class)
|
||||
.isThrownBy(() -> this.securedChannelAdapter2.send(new GenericMessage<>("test")))
|
||||
.withRootCauseExactlyInstanceOf(AccessDeniedException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecuredWithoutAuthenticating() {
|
||||
assertThatExceptionOfType(MessageDeliveryException.class)
|
||||
.isThrownBy(() -> this.securedChannelAdapter.send(new GenericMessage<>("test")))
|
||||
.withRootCauseExactlyInstanceOf(AuthenticationCredentialsNotFoundException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsecuredAsAdmin() {
|
||||
login("bob", "bobspassword", "ROLE_ADMIN");
|
||||
unsecuredChannelAdapter.send(new GenericMessage<String>("test"));
|
||||
assertThat(testConsumer.sentMessages.size()).as("Wrong size of message list in target").isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsecuredAsUser() {
|
||||
login("bob", "bobspassword", "ROLE_USER");
|
||||
unsecuredChannelAdapter.send(new GenericMessage<String>("test"));
|
||||
assertThat(testConsumer.sentMessages.size()).as("Wrong size of message list in target").isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsecuredWithoutAuthenticating() {
|
||||
unsecuredChannelAdapter.send(new GenericMessage<String>("test"));
|
||||
assertThat(testConsumer.sentMessages.size()).as("Wrong size of message list in target").isEqualTo(1);
|
||||
}
|
||||
|
||||
|
||||
private void login(String username, String password, String... roles) {
|
||||
SecurityContext context = SecurityTestUtils.createContext(username, password, roles);
|
||||
SecurityContextHolder.setContext(context);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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
|
||||
*
|
||||
* https://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 org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.security.SecurityTestUtils;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageDeliveryException;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
|
||||
import org.springframework.security.authorization.AuthorityAuthorizationManager;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.messaging.access.intercept.AuthorizationChannelInterceptor;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
public class ChannelSecurityInterceptorTests {
|
||||
|
||||
@AfterEach
|
||||
public void clearSecurityContext() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void securedSendWithoutAuthentication() {
|
||||
MessageChannel channel = getSecuredChannel();
|
||||
assertThatExceptionOfType(MessageDeliveryException.class)
|
||||
.isThrownBy(() -> channel.send(new GenericMessage<>("test")))
|
||||
.withRootCauseExactlyInstanceOf(AuthenticationCredentialsNotFoundException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void securedSendWithoutRole() {
|
||||
MessageChannel channel = getSecuredChannel();
|
||||
SecurityContext context = SecurityTestUtils.createContext("test", "pwd", "ROLE_USER");
|
||||
SecurityContextHolder.setContext(context);
|
||||
assertThatExceptionOfType(MessageDeliveryException.class)
|
||||
.isThrownBy(() -> channel.send(new GenericMessage<>("test")))
|
||||
.withRootCauseExactlyInstanceOf(AccessDeniedException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void securedSendWithRole() {
|
||||
MessageChannel channel = getSecuredChannel();
|
||||
SecurityContext context = SecurityTestUtils.createContext("test", "pwd", "ROLE_ADMIN");
|
||||
SecurityContextHolder.setContext(context);
|
||||
channel.send(new GenericMessage<>("test"));
|
||||
}
|
||||
|
||||
|
||||
private static MessageChannel getSecuredChannel() {
|
||||
QueueChannel channel = new QueueChannel();
|
||||
channel.setBeanName("securedChannel");
|
||||
channel.addInterceptor(new AuthorizationChannelInterceptor(AuthorityAuthorizationManager.hasRole("ADMIN")));
|
||||
return channel;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,423 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 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
|
||||
*
|
||||
* https://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.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
||||
import org.springframework.core.task.AsyncTaskExecutor;
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.integration.IntegrationMessageHeaderAccessor;
|
||||
import org.springframework.integration.annotation.BridgeTo;
|
||||
import org.springframework.integration.annotation.Gateway;
|
||||
import org.springframework.integration.annotation.IntegrationComponentScan;
|
||||
import org.springframework.integration.annotation.MessagingGateway;
|
||||
import org.springframework.integration.annotation.Poller;
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.ExecutorChannel;
|
||||
import org.springframework.integration.channel.PublishSubscribeChannel;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.config.EnableIntegration;
|
||||
import org.springframework.integration.config.GlobalChannelInterceptor;
|
||||
import org.springframework.integration.handler.BridgeHandler;
|
||||
import org.springframework.integration.security.SecurityTestUtils;
|
||||
import org.springframework.integration.security.TestHandler;
|
||||
import org.springframework.integration.security.channel.SecurityContextPropagationChannelInterceptor;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageDeliveryException;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.messaging.support.ChannelInterceptor;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
|
||||
import org.springframework.security.authorization.AuthorityAuthorizationManager;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.messaging.access.intercept.AuthorizationChannelInterceptor;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.task.DelegatingSecurityContextAsyncTaskExecutor;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
@SpringJUnitConfig
|
||||
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
|
||||
public class ChannelSecurityAnnotationTests {
|
||||
|
||||
@Autowired
|
||||
MessageChannel securedChannel;
|
||||
|
||||
@Autowired
|
||||
MessageChannel securedChannel2;
|
||||
|
||||
@Autowired
|
||||
MessageChannel unsecuredChannel;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("queueChannel")
|
||||
MessageChannel queueChannel;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("securedChannelQueue")
|
||||
PollableChannel securedChannelQueue;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("executorChannel")
|
||||
MessageChannel executorChannel;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("publishSubscribeChannel")
|
||||
PublishSubscribeChannel publishSubscribeChannel;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("securedChannelQueue2")
|
||||
PollableChannel securedChannelQueue2;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("errorChannel")
|
||||
PollableChannel errorChannel;
|
||||
|
||||
@Autowired
|
||||
TestHandler testConsumer;
|
||||
|
||||
@Autowired
|
||||
TestGateway testGateway;
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSecuredWithNotEnoughPermission() {
|
||||
login("bob", "bobspassword", "ROLE_ADMINA");
|
||||
assertThatExceptionOfType(MessageDeliveryException.class)
|
||||
.isThrownBy(() -> this.securedChannel.send(new GenericMessage<>("test")))
|
||||
.withRootCauseExactlyInstanceOf(AccessDeniedException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecuredWithPermission() {
|
||||
login("bob", "bobspassword", "ROLE_ADMIN", "ROLE_PRESIDENT");
|
||||
securedChannel.send(new GenericMessage<>("test"));
|
||||
securedChannel2.send(new GenericMessage<>("test"));
|
||||
assertThat(testConsumer.sentMessages.size()).as("Wrong size of message list in target").isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecuredWithoutPermision() {
|
||||
login("bob", "bobspassword", "ROLE_USER");
|
||||
assertThatExceptionOfType(MessageDeliveryException.class)
|
||||
.isThrownBy(() -> this.securedChannel.send(new GenericMessage<>("test")))
|
||||
.withRootCauseExactlyInstanceOf(AccessDeniedException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecured2WithoutPermision() {
|
||||
login("bob", "bobspassword", "ROLE_USER");
|
||||
assertThatExceptionOfType(MessageDeliveryException.class)
|
||||
.isThrownBy(() -> this.securedChannel2.send(new GenericMessage<>("test")))
|
||||
.withRootCauseExactlyInstanceOf(AccessDeniedException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecuredWithoutAuthenticating() {
|
||||
assertThatExceptionOfType(MessageDeliveryException.class)
|
||||
.isThrownBy(() -> this.securedChannel2.send(new GenericMessage<>("test")))
|
||||
.withRootCauseExactlyInstanceOf(AuthenticationCredentialsNotFoundException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsecuredAsAdmin() {
|
||||
login("bob", "bobspassword", "ROLE_ADMIN");
|
||||
unsecuredChannel.send(new GenericMessage<>("test"));
|
||||
assertThat(testConsumer.sentMessages.size()).as("Wrong size of message list in target").isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsecuredAsUser() {
|
||||
login("bob", "bobspassword", "ROLE_USER");
|
||||
unsecuredChannel.send(new GenericMessage<>("test"));
|
||||
assertThat(testConsumer.sentMessages.size()).as("Wrong size of message list in target").isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsecuredWithoutAuthenticating() {
|
||||
unsecuredChannel.send(new GenericMessage<>("test"));
|
||||
assertThat(testConsumer.sentMessages.size()).as("Wrong size of message list in target").isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecurityContextPropagationQueueChannel() {
|
||||
login("bob", "bobspassword", "ROLE_ADMIN", "ROLE_PRESIDENT");
|
||||
this.queueChannel.send(new GenericMessage<>("test"));
|
||||
Message<?> receive = this.securedChannelQueue.receive(10000);
|
||||
assertThat(receive).isNotNull();
|
||||
|
||||
SecurityContextHolder.clearContext();
|
||||
|
||||
this.queueChannel.send(new GenericMessage<>("test"));
|
||||
Message<?> errorMessage = this.errorChannel.receive(10000);
|
||||
assertThat(errorMessage).isNotNull();
|
||||
Object payload = errorMessage.getPayload();
|
||||
assertThat(payload).isInstanceOf(MessageDeliveryException.class);
|
||||
assertThat(((MessageDeliveryException) payload).getCause())
|
||||
.isInstanceOf(AuthenticationCredentialsNotFoundException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecurityContextPropagationExecutorChannel() {
|
||||
login("bob", "bobspassword", "ROLE_ADMIN", "ROLE_PRESIDENT");
|
||||
this.executorChannel.send(new GenericMessage<>("test"));
|
||||
Message<?> receive = this.securedChannelQueue.receive(10000);
|
||||
assertThat(receive).isNotNull();
|
||||
|
||||
SecurityContextHolder.clearContext();
|
||||
|
||||
this.executorChannel.send(new GenericMessage<>("test"));
|
||||
Message<?> errorMessage = this.errorChannel.receive(10000);
|
||||
assertThat(errorMessage).isNotNull();
|
||||
Object payload = errorMessage.getPayload();
|
||||
assertThat(payload).isInstanceOf(MessageDeliveryException.class);
|
||||
assertThat(((MessageDeliveryException) payload).getCause())
|
||||
.isInstanceOf(AuthenticationCredentialsNotFoundException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecurityContextPropagationPublishSubscribeChannel() {
|
||||
login("bob", "bobspassword", "ROLE_ADMIN", "ROLE_PRESIDENT");
|
||||
|
||||
this.publishSubscribeChannel.send(new GenericMessage<>("test"));
|
||||
|
||||
Message<?> receive = this.securedChannelQueue.receive(10000);
|
||||
assertThat(receive).isNotNull();
|
||||
IntegrationMessageHeaderAccessor headerAccessor = new IntegrationMessageHeaderAccessor(receive);
|
||||
assertThat(headerAccessor.getSequenceNumber()).isEqualTo(0);
|
||||
|
||||
receive = this.securedChannelQueue2.receive(10000);
|
||||
assertThat(receive).isNotNull();
|
||||
headerAccessor = new IntegrationMessageHeaderAccessor(receive);
|
||||
assertThat(headerAccessor.getSequenceNumber()).isEqualTo(0);
|
||||
|
||||
this.publishSubscribeChannel.setApplySequence(true);
|
||||
|
||||
this.publishSubscribeChannel.send(new GenericMessage<>("test"));
|
||||
|
||||
receive = this.securedChannelQueue.receive(10000);
|
||||
assertThat(receive).isNotNull();
|
||||
headerAccessor = new IntegrationMessageHeaderAccessor(receive);
|
||||
assertThat(headerAccessor.getSequenceNumber()).isEqualTo(1);
|
||||
|
||||
receive = this.securedChannelQueue2.receive(10000);
|
||||
assertThat(receive).isNotNull();
|
||||
headerAccessor = new IntegrationMessageHeaderAccessor(receive);
|
||||
assertThat(headerAccessor.getSequenceNumber()).isEqualTo(2);
|
||||
|
||||
this.publishSubscribeChannel.setApplySequence(false);
|
||||
|
||||
SecurityContextHolder.clearContext();
|
||||
|
||||
this.publishSubscribeChannel.send(new GenericMessage<>("test"));
|
||||
Message<?> errorMessage = this.errorChannel.receive(10000);
|
||||
assertThat(errorMessage).isNotNull();
|
||||
Object payload = errorMessage.getPayload();
|
||||
assertThat(payload).isInstanceOf(MessageDeliveryException.class);
|
||||
assertThat(((MessageDeliveryException) payload).getCause())
|
||||
.isInstanceOf(AuthenticationCredentialsNotFoundException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecurityContextPropagationAsyncGateway() throws Exception {
|
||||
login("bob", "bobspassword", "ROLE_ADMIN", "ROLE_PRESIDENT");
|
||||
Future<String> future = this.testGateway.test("foo");
|
||||
Message<?> receive = this.securedChannelQueue.receive(10000);
|
||||
assertThat(receive).isNotNull();
|
||||
|
||||
MessageChannel replyChannel = receive.getHeaders().get(MessageHeaders.REPLY_CHANNEL, MessageChannel.class);
|
||||
replyChannel.send(new GenericMessage<>("bar"));
|
||||
|
||||
String result = future.get(10, TimeUnit.SECONDS);
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result).isEqualTo("bar");
|
||||
}
|
||||
|
||||
private void login(String username, String password, String... roles) {
|
||||
SecurityContext context = SecurityTestUtils.createContext(username, password, roles);
|
||||
SecurityContextHolder.setContext(context);
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@EnableIntegration
|
||||
@IntegrationComponentScan
|
||||
public static class ContextConfiguration {
|
||||
|
||||
@Bean
|
||||
UserDetailsService userDetailsService() {
|
||||
return new InMemoryUserDetailsManager(
|
||||
User.withUsername("jimi")
|
||||
.password("jimispassword")
|
||||
.authorities("ROLE_USER", "ROLE_ADMIN")
|
||||
.build(),
|
||||
User.withUsername("bob")
|
||||
.password("bobspassword")
|
||||
.authorities("ROLE_USER")
|
||||
.build());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
|
||||
return new PropertySourcesPlaceholderConfigurer();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@GlobalChannelInterceptor(patterns = "secured*")
|
||||
AuthorizationChannelInterceptor authorizationChannelInterceptor() {
|
||||
return new AuthorizationChannelInterceptor(AuthorityAuthorizationManager.hasAnyRole("ADMIN", "PRESIDENT"));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SubscribableChannel securedChannel() {
|
||||
return new DirectChannel();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SubscribableChannel securedChannel2() {
|
||||
return new DirectChannel();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SubscribableChannel unsecuredChannel() {
|
||||
return new DirectChannel();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@GlobalChannelInterceptor(patterns = {
|
||||
"#{'queueChannel'}",
|
||||
"${security.channel:executorChannel}",
|
||||
"publishSubscribeChannel"})
|
||||
public ChannelInterceptor securityContextPropagationInterceptor() {
|
||||
return new SecurityContextPropagationChannelInterceptor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@BridgeTo(value = "securedChannelQueue", poller = @Poller(fixedDelay = "1000"))
|
||||
public PollableChannel queueChannel() {
|
||||
return new QueueChannel();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PollableChannel securedChannelQueue() {
|
||||
return new QueueChannel();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@BridgeTo("securedChannelQueue")
|
||||
public SubscribableChannel executorChannel() {
|
||||
return new ExecutorChannel(Executors.newSingleThreadExecutor());
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public PublishSubscribeChannel publishSubscribeChannel() {
|
||||
return new PublishSubscribeChannel(Executors.newCachedThreadPool());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ServiceActivator(inputChannel = "publishSubscribeChannel")
|
||||
public MessageHandler securedChannelQueueBridge() {
|
||||
BridgeHandler handler = new BridgeHandler();
|
||||
handler.setOutputChannel(securedChannelQueue());
|
||||
handler.setOrder(1);
|
||||
return handler;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PollableChannel securedChannelQueue2() {
|
||||
return new QueueChannel();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ServiceActivator(inputChannel = "publishSubscribeChannel")
|
||||
public MessageHandler securedChannelQueue2Bridge() {
|
||||
BridgeHandler handler = new BridgeHandler();
|
||||
handler.setOutputChannel(securedChannelQueue2());
|
||||
handler.setOrder(2);
|
||||
return handler;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TaskScheduler taskScheduler() {
|
||||
return new ThreadPoolTaskScheduler();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PollableChannel errorChannel() {
|
||||
return new QueueChannel();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TestHandler testHandler() {
|
||||
TestHandler testHandler = new TestHandler();
|
||||
this.securedChannel().subscribe(testHandler);
|
||||
this.securedChannel2().subscribe(testHandler);
|
||||
this.unsecuredChannel().subscribe(testHandler);
|
||||
return testHandler;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AsyncTaskExecutor securityContextExecutor() {
|
||||
return new DelegatingSecurityContextAsyncTaskExecutor(new SimpleAsyncTaskExecutor());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@MessagingGateway(asyncExecutor = "securityContextExecutor")
|
||||
public interface TestGateway {
|
||||
|
||||
@Gateway(requestChannel = "queueChannel")
|
||||
Future<String> test(String payload);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="WARN">
|
||||
<Appenders>
|
||||
<Console name="STDOUT" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="%d %p [%t] [%c] - %m%n" />
|
||||
</Console>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Logger name="org.springframework.integration" level="warn"/>
|
||||
<Logger name="org.springframework.integration.security" level="warn"/>
|
||||
<Root level="warn">
|
||||
<AppenderRef ref="STDOUT" />
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
@@ -10,30 +10,10 @@ Spring Integration, together with https://projects.spring.io/spring-security/[Sp
|
||||
Starting with version 6.0, the `ChannelSecurityInterceptor` as well as its configuration via `@SecuredChannel` annotation and XML `<secured-channels>` have been deprecation in favor of using `AuthorizationChannelInterceptor` from the `spring-security-messaging` module.
|
||||
The respective `AuthorizationManager` infrastructure fully covers the previously supported role-based authentication, plus it allows the configuration of any other possible authorization strategies.
|
||||
|
||||
The only remaining Spring Integration feature is a `SecurityContextPropagationChannelInterceptor` which may be promoted to the mentioned `spring-security-messaging` module in the future as well.
|
||||
The only remaining Spring Integration `SecurityContextPropagationChannelInterceptor` class has been deprecated and promoted to the mentioned `spring-security-messaging` module as an `org.springframework.security.messaging.context.SecurityContextPropagationChannelInterceptor` class.
|
||||
|
||||
You need to include this dependency into your project:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Maven::
|
||||
+
|
||||
[source, xml, subs="normal", role="primary"]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-security</artifactId>
|
||||
<version>{project-version}</version>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
Gradle::
|
||||
+
|
||||
[source, groovy, subs="normal", role="secondary"]
|
||||
----
|
||||
compile "org.springframework.integration:spring-integration-security:{project-version}"
|
||||
----
|
||||
======
|
||||
Therefore, starting with version `6.2` the whole `spring-integration-security` module is considered as deprecated in favor of an API proposed by the more common `spring-security-messaging` library.
|
||||
This module is scheduled for removal in the next `6.3` version.
|
||||
|
||||
[[securing-channels]]
|
||||
== Securing channels
|
||||
@@ -98,19 +78,12 @@ In order to support such scenarios, we have two choices:
|
||||
* Transfer an `Authentication` object within the message headers and extract and authenticate it on the other side before secured object access.
|
||||
* Propagate the `SecurityContext` to the thread that receives the transferred message.
|
||||
|
||||
Version 4.2 introduced `SecurityContext` propagation.
|
||||
It is implemented as a `SecurityContextPropagationChannelInterceptor`, which you can add to any `MessageChannel` or configure as a `@GlobalChannelInterceptor`.
|
||||
This is implemented as a `org.springframework.security.messaging.context.SecurityContextPropagationChannelInterceptor` in the `spring-security-messaging` module, which can be added to any `MessageChannel` or configured as a `@GlobalChannelInterceptor`.
|
||||
The logic of this interceptor is based on the `SecurityContext` extraction from the current thread (from the `preSend()` method) and its populating to another thread from the `postReceive()` (`beforeHandle()`) method.
|
||||
Actually, this interceptor is an extension of the more generic `ThreadStatePropagationChannelInterceptor`, which wraps the message to send with the state to propagate in an internal `Message<?>` extension (`MessageWithThreadState<S>`) on one side and extracts the original message and the state to propagate on the other side.
|
||||
You can extend the `ThreadStatePropagationChannelInterceptor` for any context propagation use case, and `SecurityContextPropagationChannelInterceptor` is a good example of doing so.
|
||||
|
||||
IMPORTANT: The logic of the `ThreadStatePropagationChannelInterceptor` is based on message modification (it returns an internal `MessageWithThreadState` object to send).
|
||||
Consequently, you should be careful when combining this interceptor with any other that can also modify messages (for example, through the `MessageBuilder.withPayload(...)...build()`).
|
||||
The state to propagate may be lost.
|
||||
In most cases, to overcome the issue, you can order the interceptors for the channel and ensure the `ThreadStatePropagationChannelInterceptor` is the last one in the stack.
|
||||
See the `SecurityContextPropagationChannelInterceptor` Javadocs for more information.
|
||||
|
||||
Propagation and population of `SecurityContext` is just one half of the work.
|
||||
Since the message is not an owner of the threads in the message flow and we should be sure that we are secure against any incoming messages, we have to clean up the `SecurityContext` from `ThreadLocal`.
|
||||
Since the message is not an owner of the threads in the message flow, and the system should be sure that it is secured against any incoming messages, the `SecurityContext` has to be cleaned up from `ThreadLocal`.
|
||||
The `SecurityContextPropagationChannelInterceptor` provides the `afterMessageHandled()` interceptor method implementation.
|
||||
It cleans up operation by freeing the thread at the end of invocation from that propagated principal.
|
||||
This means that, when the thread that processes the handed-off message finishes processing the message (successful or otherwise), the context is cleared so that it cannot inadvertently be used when processing another message.
|
||||
|
||||
@@ -83,3 +83,9 @@ See xref:ftp/inbound.adoc#ftp-inbound[FTP Inbound Channel Adapter], xref:sftp/in
|
||||
|
||||
A new `DefaultSftpSessionFactory.createSftpClient(...)` method has been introduced to support a custom `SftpClient` when overridden.
|
||||
See xref:sftp/session-factory.adoc#sftp-session-factory[SFTP Session Factory] for more information.
|
||||
|
||||
[[x6.2-security-changes]]
|
||||
=== Security Support Changes
|
||||
|
||||
The last class in `spring-integration-security` module `SecurityContextPropagationChannelInterceptor` has been deprecated in favor of similar class moved to `spring-security-messaging` module.
|
||||
See xref:security.adoc[Security in Spring Integration] for more information.
|
||||
Reference in New Issue
Block a user