INT-4198: TCP: Add Hook to Customize SSLEngine
JIRA: https://jira.spring.io/browse/INT-4198 Enable setting properties like `needClientAuth` on the `SSLEngine` - when not using NIO, this can be set on the server socket with a socket support implementation. Add `nio-connection-support` to namespace. Improved "Advanced Techniques" documentation, using this use case as an example. Fail fast with NIO when SSL handshaking fails. Polishing - PR Comments More Polishing * Final polishing - fix several typos in log messages - clean up `TcpConnectionFactoryFactoryBean` JavaDocs from redundant imports - remove redundant `InitializationBean` functionality from the `DefaultTcpNetSSLSocketFactorySupport` as well
This commit is contained in:
committed by
Artem Bilan
parent
bdab0aa1d3
commit
a0f0b6ab64
@@ -72,6 +72,7 @@
|
||||
using-nio="true"
|
||||
ssl-context-support="sslContextSupport"
|
||||
ssl-handshake-timeout="43"
|
||||
nio-connection-support="nioConnectionSupport"
|
||||
/>
|
||||
|
||||
<bean id="sslContextSupport" class="org.springframework.integration.ip.tcp.connection.DefaultTcpSSLContextSupport">
|
||||
@@ -81,6 +82,9 @@
|
||||
<constructor-arg value="secret"/>
|
||||
</bean>
|
||||
|
||||
<bean id="nioConnectionSupport"
|
||||
class="org.springframework.integration.ip.tcp.connection.DefaultTcpNioSSLConnectionSupport" />
|
||||
|
||||
<ip:tcp-connection-factory id="secureServer"
|
||||
type="server"
|
||||
port="#{tcpIpUtils.findAvailableServerSocket(5250)}"
|
||||
|
||||
@@ -36,7 +36,6 @@ import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.UrlResource;
|
||||
import org.springframework.core.serializer.Deserializer;
|
||||
import org.springframework.core.serializer.Serializer;
|
||||
@@ -324,29 +323,26 @@ public class ParserUnitTests {
|
||||
assertEquals(124, tcpIn.getPhase());
|
||||
TcpMessageMapper cfS1Mapper = TestUtils.getPropertyValue(cfS1, "mapper", TcpMessageMapper.class);
|
||||
assertSame(mapper, cfS1Mapper);
|
||||
assertTrue((Boolean) TestUtils.getPropertyValue(cfS1Mapper, "applySequence"));
|
||||
assertTrue(TestUtils.getPropertyValue(cfS1Mapper, "applySequence", Boolean.class));
|
||||
Object socketSupport = TestUtils.getPropertyValue(cfS1, "tcpSocketFactorySupport");
|
||||
assertTrue(socketSupport instanceof DefaultTcpNetSSLSocketFactorySupport);
|
||||
assertNotNull(TestUtils.getPropertyValue(socketSupport, "sslContext"));
|
||||
|
||||
TcpSSLContextSupport contextSupport = TestUtils.getPropertyValue(cfS1, "tcpSocketFactorySupport.sslContextSupport", TcpSSLContextSupport.class);
|
||||
assertSame(contextSupport, this.contextSupport);
|
||||
assertTrue(TestUtils.getPropertyValue(contextSupport, "keyStore") instanceof ClassPathResource);
|
||||
assertTrue(TestUtils.getPropertyValue(contextSupport, "trustStore") instanceof ClassPathResource);
|
||||
|
||||
contextSupport = new DefaultTcpSSLContextSupport("http:foo", "file:bar", "", "");
|
||||
assertTrue(TestUtils.getPropertyValue(contextSupport, "keyStore") instanceof UrlResource);
|
||||
assertTrue(TestUtils.getPropertyValue(contextSupport, "trustStore") instanceof UrlResource);
|
||||
TcpSSLContextSupport tcpSSLContextSupport = new DefaultTcpSSLContextSupport("http:foo", "file:bar", "", "");
|
||||
assertTrue(TestUtils.getPropertyValue(tcpSSLContextSupport, "keyStore") instanceof UrlResource);
|
||||
assertTrue(TestUtils.getPropertyValue(tcpSSLContextSupport, "trustStore") instanceof UrlResource);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInTcpNioSSLDefaultConfig() {
|
||||
assertFalse(cfS1Nio.isLookupHost());
|
||||
assertTrue((Boolean) TestUtils.getPropertyValue(cfS1Nio, "mapper.applySequence"));
|
||||
assertTrue(TestUtils.getPropertyValue(cfS1Nio, "mapper.applySequence", Boolean.class));
|
||||
Object connectionSupport = TestUtils.getPropertyValue(cfS1Nio, "tcpNioConnectionSupport");
|
||||
assertTrue(connectionSupport instanceof DefaultTcpNioSSLConnectionSupport);
|
||||
assertNotNull(TestUtils.getPropertyValue(connectionSupport, "sslContext"));
|
||||
assertEquals(43, TestUtils.getPropertyValue(this.cfS1Nio, "sslHandshakeTimeout"));
|
||||
assertSame(this.ctx.getBean(DefaultTcpNioSSLConnectionSupport.class),
|
||||
TestUtils.getPropertyValue(this.cfS1Nio, "tcpNioConnectionSupport"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -16,17 +16,23 @@
|
||||
|
||||
package org.springframework.integration.ip.tcp.connection;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -39,6 +45,8 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
import javax.net.ServerSocketFactory;
|
||||
import javax.net.SocketFactory;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.SSLServerSocket;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
@@ -274,7 +282,6 @@ Certificate fingerprints:
|
||||
"test.truststore.ks", "secret", "secret");
|
||||
DefaultTcpNetSSLSocketFactorySupport tcpSocketFactorySupport =
|
||||
new DefaultTcpNetSSLSocketFactorySupport(sslContextSupport);
|
||||
tcpSocketFactorySupport.afterPropertiesSet();
|
||||
server.setTcpSocketFactorySupport(tcpSocketFactorySupport);
|
||||
final List<Message<?>> messages = new ArrayList<Message<?>>();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
@@ -303,13 +310,23 @@ Certificate fingerprints:
|
||||
|
||||
@Test
|
||||
public void testNetClientAndServerSSLDifferentContexts() throws Exception {
|
||||
testNetClientAndServerSSLDifferentContexts(false);
|
||||
try {
|
||||
testNetClientAndServerSSLDifferentContexts(true);
|
||||
fail("expected Exception");
|
||||
}
|
||||
catch (SSLException | SocketException e) {
|
||||
// NOSONAR
|
||||
}
|
||||
}
|
||||
|
||||
private void testNetClientAndServerSSLDifferentContexts(boolean badClient) throws Exception {
|
||||
System.setProperty("javax.net.debug", "all"); // SSL activity in the console
|
||||
TcpNetServerConnectionFactory server = new TcpNetServerConnectionFactory(0);
|
||||
TcpSSLContextSupport serverSslContextSupport = new DefaultTcpSSLContextSupport("server.ks",
|
||||
"server.truststore.ks", "secret", "secret");
|
||||
DefaultTcpNetSSLSocketFactorySupport serverTcpSocketFactorySupport =
|
||||
new DefaultTcpNetSSLSocketFactorySupport(serverSslContextSupport);
|
||||
serverTcpSocketFactorySupport.afterPropertiesSet();
|
||||
server.setTcpSocketFactorySupport(serverTcpSocketFactorySupport);
|
||||
final List<Message<?>> messages = new ArrayList<Message<?>>();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
@@ -318,15 +335,23 @@ Certificate fingerprints:
|
||||
latch.countDown();
|
||||
return false;
|
||||
});
|
||||
server.setTcpSocketSupport(new DefaultTcpSocketSupport() {
|
||||
|
||||
@Override
|
||||
public void postProcessServerSocket(ServerSocket serverSocket) {
|
||||
((SSLServerSocket) serverSocket).setNeedClientAuth(true);
|
||||
}
|
||||
|
||||
});
|
||||
server.start();
|
||||
TestingUtilities.waitListening(server, null);
|
||||
|
||||
TcpNetClientConnectionFactory client = new TcpNetClientConnectionFactory("localhost", server.getPort());
|
||||
TcpSSLContextSupport clientSslContextSupport = new DefaultTcpSSLContextSupport("client.ks",
|
||||
TcpSSLContextSupport clientSslContextSupport = new DefaultTcpSSLContextSupport(
|
||||
badClient ? "server.ks" : "client.ks",
|
||||
"client.truststore.ks", "secret", "secret");
|
||||
DefaultTcpNetSSLSocketFactorySupport clientTcpSocketFactorySupport =
|
||||
new DefaultTcpNetSSLSocketFactorySupport(clientSslContextSupport);
|
||||
clientTcpSocketFactorySupport.afterPropertiesSet();
|
||||
client.setTcpSocketFactorySupport(clientTcpSocketFactorySupport);
|
||||
client.start();
|
||||
|
||||
@@ -349,7 +374,6 @@ Certificate fingerprints:
|
||||
sslContextSupport.setProtocol("SSL");
|
||||
DefaultTcpNioSSLConnectionSupport tcpNioConnectionSupport =
|
||||
new DefaultTcpNioSSLConnectionSupport(sslContextSupport);
|
||||
tcpNioConnectionSupport.afterPropertiesSet();
|
||||
server.setTcpNioConnectionSupport(tcpNioConnectionSupport);
|
||||
final List<Message<?>> messages = new ArrayList<Message<?>>();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
@@ -391,6 +415,63 @@ Certificate fingerprints:
|
||||
server.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNioClientAndServerSSLDifferentContexts() throws Exception {
|
||||
testNioClientAndServerSSLDifferentContexts(false);
|
||||
try {
|
||||
testNioClientAndServerSSLDifferentContexts(true);
|
||||
fail("expected Exception");
|
||||
}
|
||||
catch (IOException e) {
|
||||
if (!(e instanceof ClosedChannelException)) {
|
||||
assertThat(e.getMessage(), containsString("Socket closed during SSL Handshake"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void testNioClientAndServerSSLDifferentContexts(boolean badClient) throws Exception {
|
||||
System.setProperty("javax.net.debug", "all"); // SSL activity in the console
|
||||
TcpNioServerConnectionFactory server = new TcpNioServerConnectionFactory(0);
|
||||
TcpSSLContextSupport serverSslContextSupport = new DefaultTcpSSLContextSupport("server.ks",
|
||||
"server.truststore.ks", "secret", "secret");
|
||||
DefaultTcpNioSSLConnectionSupport tcpNioConnectionSupport =
|
||||
new DefaultTcpNioSSLConnectionSupport(serverSslContextSupport) {
|
||||
|
||||
@Override
|
||||
protected void postProcessSSLEngine(SSLEngine sslEngine) {
|
||||
sslEngine.setNeedClientAuth(true);
|
||||
}
|
||||
|
||||
};
|
||||
server.setTcpNioConnectionSupport(tcpNioConnectionSupport);
|
||||
final List<Message<?>> messages = new ArrayList<Message<?>>();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
server.registerListener(message -> {
|
||||
messages.add(message);
|
||||
latch.countDown();
|
||||
return false;
|
||||
});
|
||||
server.start();
|
||||
TestingUtilities.waitListening(server, null);
|
||||
|
||||
TcpNioClientConnectionFactory client = new TcpNioClientConnectionFactory("localhost", server.getPort());
|
||||
TcpSSLContextSupport clientSslContextSupport = new DefaultTcpSSLContextSupport(
|
||||
badClient ? "server.ks" : "client.ks",
|
||||
"client.truststore.ks", "secret", "secret");
|
||||
DefaultTcpNioSSLConnectionSupport clientTcpNioConnectionSupport =
|
||||
new DefaultTcpNioSSLConnectionSupport(clientSslContextSupport);
|
||||
client.setTcpNioConnectionSupport(clientTcpNioConnectionSupport);
|
||||
client.start();
|
||||
|
||||
TcpConnection connection = client.getConnection();
|
||||
connection.send(new GenericMessage<String>("Hello, world!"));
|
||||
assertTrue(latch.await(10, TimeUnit.SECONDS));
|
||||
assertEquals("Hello, world!", new String((byte[]) messages.get(0).getPayload()));
|
||||
|
||||
client.stop();
|
||||
server.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNioClientAndServerSSLDifferentContextsLargeDataWithReply() throws Exception {
|
||||
System.setProperty("javax.net.debug", "all"); // SSL activity in the console
|
||||
@@ -399,7 +480,6 @@ Certificate fingerprints:
|
||||
"server.truststore.ks", "secret", "secret");
|
||||
DefaultTcpNioSSLConnectionSupport serverTcpNioConnectionSupport =
|
||||
new DefaultTcpNioSSLConnectionSupport(serverSslContextSupport);
|
||||
serverTcpNioConnectionSupport.afterPropertiesSet();
|
||||
server.setTcpNioConnectionSupport(serverTcpNioConnectionSupport);
|
||||
final List<Message<?>> messages = new ArrayList<Message<?>>();
|
||||
final CountDownLatch latch = new CountDownLatch(2);
|
||||
@@ -411,7 +491,7 @@ Certificate fingerprints:
|
||||
replier.send(message);
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
latch.countDown();
|
||||
return false;
|
||||
@@ -433,7 +513,6 @@ Certificate fingerprints:
|
||||
"client.truststore.ks", "secret", "secret");
|
||||
DefaultTcpNioSSLConnectionSupport clientTcpNioConnectionSupport =
|
||||
new DefaultTcpNioSSLConnectionSupport(clientSslContextSupport);
|
||||
clientTcpNioConnectionSupport.afterPropertiesSet();
|
||||
client.setTcpNioConnectionSupport(clientTcpNioConnectionSupport);
|
||||
client.registerListener(message -> {
|
||||
messages.add(message);
|
||||
|
||||
@@ -382,7 +382,7 @@ public class TcpNioConnectionReadTests {
|
||||
assertTrue(errorMessageLetch.await(10, TimeUnit.SECONDS));
|
||||
|
||||
assertThat(errorMessageRef.get().getMessage(),
|
||||
containsString("Connection is closed"));
|
||||
anyOf(containsString("Connection is closed"), containsString("Stream closed after 2 of 3")));
|
||||
|
||||
assertTrue(semaphore.tryAcquire(10000, TimeUnit.MILLISECONDS));
|
||||
assertTrue(removed.size() > 0);
|
||||
@@ -473,7 +473,6 @@ public class TcpNioConnectionReadTests {
|
||||
|
||||
private void testClosureMidMessageGuts(AbstractByteArraySerializer serializer, String shortMessage)
|
||||
throws Exception {
|
||||
final List<Message<?>> responses = new ArrayList<Message<?>>();
|
||||
final Semaphore semaphore = new Semaphore(0);
|
||||
final List<TcpConnection> added = new ArrayList<TcpConnection>();
|
||||
final List<TcpConnection> removed = new ArrayList<TcpConnection>();
|
||||
@@ -500,6 +499,7 @@ public class TcpNioConnectionReadTests {
|
||||
removed.add(connection);
|
||||
semaphore.release();
|
||||
}
|
||||
|
||||
});
|
||||
Socket socket = SocketFactory.getDefault().createSocket("localhost", scf.getPort());
|
||||
socket.getOutputStream().write(shortMessage.getBytes());
|
||||
|
||||
Reference in New Issue
Block a user