diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/config/IpAdapterParserUtils.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/config/IpAdapterParserUtils.java index 630b0f5186..9e6b784ce6 100644 --- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/config/IpAdapterParserUtils.java +++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/config/IpAdapterParserUtils.java @@ -118,6 +118,8 @@ public abstract class IpAdapterParserUtils { public static final String SOCKET_SUPPORT = "socket-support"; + public static final String NIO_CONNECTION_SUPPORT = "nio-connection-support"; + public static final String SOCKET_FACTORY_SUPPORT = "socket-factory-support"; public static final String BACKLOG = "backlog"; diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/config/TcpConnectionFactoryFactoryBean.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/config/TcpConnectionFactoryFactoryBean.java index 1dc3aac70f..235f837cba 100644 --- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/config/TcpConnectionFactoryFactoryBean.java +++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/config/TcpConnectionFactoryFactoryBean.java @@ -228,14 +228,7 @@ public class TcpConnectionFactoryFactoryBean extends AbstractFactoryBean deserializer) { Assert.notNull(deserializer, "Deserializer may not be null"); @@ -382,7 +367,7 @@ public class TcpConnectionFactoryFactoryBean extends AbstractFactoryBean serializer) { Assert.notNull(serializer, "Serializer may not be null"); @@ -391,7 +376,7 @@ public class TcpConnectionFactoryFactoryBean extends AbstractFactoryBean + + + + A reference to a TcpNioConnectionSupport strategy implementation. + When 'using-nio' is true, this is used to create connections. + Two default implementations are provided 'DefaultTcpNioConnectionSupport' + and 'DefaultTcpNioSSLConnectionSupport' depending on whether SSL is in + use of not. + + + + + + + + diff --git a/spring-integration-ip/src/test/java/org/springframework/integration/ip/config/ParserUnitTests-context.xml b/spring-integration-ip/src/test/java/org/springframework/integration/ip/config/ParserUnitTests-context.xml index ed4c0c075e..d83a3b277e 100644 --- a/spring-integration-ip/src/test/java/org/springframework/integration/ip/config/ParserUnitTests-context.xml +++ b/spring-integration-ip/src/test/java/org/springframework/integration/ip/config/ParserUnitTests-context.xml @@ -72,6 +72,7 @@ using-nio="true" ssl-context-support="sslContextSupport" ssl-handshake-timeout="43" + nio-connection-support="nioConnectionSupport" /> @@ -81,6 +82,9 @@ + + > messages = new ArrayList>(); 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> messages = new ArrayList>(); 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> messages = new ArrayList>(); 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> messages = new ArrayList>(); + 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("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> messages = new ArrayList>(); 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); diff --git a/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/connection/TcpNioConnectionReadTests.java b/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/connection/TcpNioConnectionReadTests.java index b319eea3bd..f7299d5dc5 100644 --- a/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/connection/TcpNioConnectionReadTests.java +++ b/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/connection/TcpNioConnectionReadTests.java @@ -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> responses = new ArrayList>(); final Semaphore semaphore = new Semaphore(0); final List added = new ArrayList(); final List removed = new ArrayList(); @@ -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()); diff --git a/src/reference/asciidoc/ip.adoc b/src/reference/asciidoc/ip.adoc index a9e913a163..8a17fa780e 100644 --- a/src/reference/asciidoc/ip.adoc +++ b/src/reference/asciidoc/ip.adoc @@ -886,17 +886,20 @@ After establishing the key stores, the next step is to indicate their locations type="client" host="localhost" port="1234" - ssl-context-support="sslContextSupport" + ssl-context-support="sslContextSupport" /> ---- -The `DefaulTcpSSLContextSupport` class also has an optional 'protocol' property, which can be 'SSL' or 'TLS' (default). +The `DefaulTcpSSLContextSupport` class also has an optional `protocol` property, which can be `SSL` or `TLS` (default). The keystore file names (first two constructor arguments) use the Spring `Resource` abstraction; by default the files will be located on the classpath, but this can be overridden by using the `file:` prefix, to find the files on the filesystem instead. Starting with _version 4.3.6_, when using NIO, you can specify an `ssl-handshake-timeout` (seconds) on the connection factory. This timeout (default 30) is used during SSL handshake when waiting for data; if the timeout is exceeded, the process is aborted and the socket closed. -==== Advanced Techniques +[[advanced-techniques]] +=== Advanced Techniques + +==== Strategy Interfaces In many cases, the configuration described above is all that is needed to enable secure communication over TCP/IP. However, a number of strategy interfaces are provided to allow customization and modification of socket factories and sockets. @@ -904,18 +907,19 @@ However, a number of strategy interfaces are provided to allow customization and * `TcpSSLContextSupport` * `TcpSocketFactorySupport` * `TcpSocketSupport` +* `TcpNioConnectionSupport` [source,java] ---- public interface TcpSSLContextSupport { - SSLContext getSSLContext() throws Exception; + SSLContext getSSLContext() throws Exception; } ---- Implementations of this interface are responsible for creating an SSLContext. -The sole implementation provided by the framework is the `DefaultTcpSSLContextSupport` described above. +The implementation provided by the framework is the `DefaultTcpSSLContextSupport` described above. If you require different behavior, implement this interface and provide the connection factory with a reference to a bean of your class' implementation. [source,java] @@ -931,8 +935,8 @@ public interface TcpSocketFactorySupport { ---- Implementations of this interface are responsible for obtaining references to `ServerSocketFactory` and `SocketFactory`. -Two implementations are provided; the first is `DefaultTcpNetSocketFactorySupport` for non-SSL sockets (when no 'ssl-context-support' attribute is defined); this simply uses the JDK's default factories. -The second implementation is `DefaultTcpNetSSLSocketFactorySupport`; this is used, by default, when an 'ssl-context-support' attribute is defined; it uses the `SSLContext` created by that bean to create the socket factories. +Two implementations are provided; the first is `DefaultTcpNetSocketFactorySupport` for non-SSL sockets (when no `ssl-context-support` attribute is defined); this simply uses the JDK's default factories. +The second implementation is `DefaultTcpNetSSLSocketFactorySupport`; this is used, by default, when an `ssl-context-support` attribute is defined; it uses the `SSLContext` created by that bean to create the socket factories. NOTE: This interface only applies if `using-nio` is "false"; socket factories are not used by NIO. @@ -944,7 +948,7 @@ public interface TcpSocketSupport { void postProcessSocket(Socket socket); - +} ---- Implementations of this interface can modify sockets after they are created, and after all configured attributes have been applied, but before the sockets are used. @@ -954,6 +958,68 @@ The sole implementation provided by the framework is the `DefaultTcpSocketSuppor To supply your own implementation of `TcpSocketFactorySupport` or `TcpSocketSupport`, provide the connection factory with references to beans of your custom type using the `socket-factory-support` and `socket-support` attributes, respectively. +[source, java] +---- +public interface TcpNioConnectionSupport { + + TcpNioConnection createNewConnection(SocketChannel socketChannel, + boolean server, boolean lookupHost, + ApplicationEventPublisher applicationEventPublisher, + String connectionFactoryName) throws Exception; + +} +---- + +This interface is invoked to create `TcpNioConnection` objects (or subclasses). +Two implementations are provided `DefaultTcpNioSSLConnectionSupport` and `DefaultTcpNioConnectionSupport` which are used depending on whether SSL is in use or not. +A common use case would be to subclass `DefaultTcpNioSSLConnectionSupport` and override `postProcessSSLEngine`; see the example below. + +==== Example: Enabling SSL Client Authentication + +To enable client certificate authentication when using SSL, the technique depends on whether NIO is in use or not. +When NIO is not being used, provide a custom `TcpSocketSupport` implementation to post-process the server socket: + +[source, java] +---- +serverFactory.setTcpSocketSupport(new DefaultTcpSocketSupport() { + + @Override + public void postProcessServerSocket(ServerSocket serverSocket) { + ((SSLServerSocket) serverSocket).setNeedClientAuth(true); + } + +}); +---- + +(When using XML configuration, provide a reference to your bean using the `socket-support` attribute). + +When using NIO, provide a custom `TcpNioSslConnectionSupport` implementation to post-process the `SSLEngine`. + +[source, java] +---- +@Bean +public DefaultTcpNioSSLConnectionSupport tcpNioConnectionSupport() { + return new DefaultTcpNioSSLConnectionSupport(serverSslContextSupport) { + + @Override + protected void postProcessSSLEngine(SSLEngine sslEngine) { + sslEngine.setNeedClientAuth(true); + } + + } +} + +@Bean +public TcpNioServerConnectionFactory server() { + ... + serverFactory.setTcpNioConnectionSupport(tcpNioConnectionSupport()); + ... +} +---- + +(When using XML configuration, since _version 4.3.7_, provide a reference to your bean using the `nio-connection-support` attribute). + + [[ip-endpoint-reference]] === IP Configuration Attributes @@ -1116,6 +1182,11 @@ Defaults to true. | Y | | See <> +| nio-connection-support +| Y +| Y +| +| See <> | read-delay | Y | Y