Advanced Configuration

DefaultFtpSessionFactory`为底层客户端 API 提供抽象,该 API(自 Spring Integration 2.0 起)即为 Apache Commons Net。这会让您免去 `org.apache.commons.net.ftp.FTPClient`的底层配置细节。会话工厂中公开了几个常见属性(从 4.0 版本开始,现在包括 `connectTimeoutdefaultTimeout`和 `dataTimeout)。但是,有时您需要访问更底层的 FTPClient`配置以实现更高级的配置(例如为主动模式设置端口范围)。为此,`AbstractFtpSessionFactory(所有 FTP 会话工厂的基本类)公开挂钩,形式为以下清单中所示的两种后处理方法:

DefaultFtpSessionFactory provides an abstraction over the underlying client API, which (since Spring Integration 2.0) is Apache Commons Net. This spares you from the low-level configuration details of the org.apache.commons.net.ftp.FTPClient. Several common properties are exposed on the session factory (since version 4.0, this now includes connectTimeout, defaultTimeout, and dataTimeout). However, you sometimes need access to lower level FTPClient configuration to achieve more advanced configuration (such as setting the port range for active mode). For that purpose, AbstractFtpSessionFactory (the base class for all FTP Session Factories) exposes hooks, in the form of the two post-processing methods shown in the following listing:

/**
 * Will handle additional initialization after client.connect() method was invoked,
 * but before any action on the client has been taken
 */
protected void postProcessClientAfterConnect(T t) throws IOException {
    // NOOP
}
/**
 * Will handle additional initialization before client.connect() method was invoked.
 */
protected void postProcessClientBeforeConnect(T client) throws IOException {
    // NOOP
}

如你所见,这两个方法没有默认实现。然而,通过扩展 DefaultFtpSessionFactory,你可以覆盖这些方法以提供 FTPClient 的更高级配置,如下面的示例所示:

As you can see, there is no default implementation for these two methods. However, by extending DefaultFtpSessionFactory, you can override these methods to provide more advanced configuration of the FTPClient, as the following example shows:

public class AdvancedFtpSessionFactory extends DefaultFtpSessionFactory {

    protected void postProcessClientBeforeConnect(FTPClient ftpClient) throws IOException {
       ftpClient.setActivePortRange(4000, 5000);
    }
}

FTPS and Shared SSLSession

使用 SSL 或 TLS 进行 FTP 时,某些服务器要求在控制和数据连接上使用相同的 SSLSession。这是为了防止 "`stealing`"数据连接。有关详细信息,请参见 [role="bare"][role="bare"]https://scarybeastsecurity.blogspot.cz/2009/02/vsftpd-210-released.html。

When using FTP over SSL or TLS, some servers require the same SSLSession to be used on the control and data connections. This is to prevent “stealing” data connections. See [role="bare"]https://scarybeastsecurity.blogspot.cz/2009/02/vsftpd-210-released.html for more information.

目前,Apache FTPSClient 不支持此功能。请参见 NET-408

Currently, the Apache FTPSClient does not support this feature. See NET-408.

以下解决方案由 Stack Overflow提供,它对 `sun.security.ssl.SSLSessionContextImpl`使用反射,因此可能无法在其他 JVM 上运行。堆栈溢出答案是在 2015 年提交的,Spring Integration 团队已在 JDK 1.8.0_112 上对该解决方案进行了测试。

The following solution, courtesy of Stack Overflow, uses reflection on the sun.security.ssl.SSLSessionContextImpl, so it may not work on other JVMs. The stack overflow answer was submitted in 2015, and the solution has been tested by the Spring Integration team on JDK 1.8.0_112.

下面的示例展示了如何创建一个 FTPS 会话:

The following example shows how to create an FTPS session:

@Bean
public DefaultFtpsSessionFactory sf() {
    DefaultFtpsSessionFactory sf = new DefaultFtpsSessionFactory() {

        @Override
        protected FTPSClient createClientInstance() {
            return new SharedSSLFTPSClient();
        }

    };
    sf.setHost("...");
    sf.setPort(21);
    sf.setUsername("...");
    sf.setPassword("...");
    sf.setNeedClientAuth(true);
    return sf;
}

private static final class SharedSSLFTPSClient extends FTPSClient {

    @Override
    protected void _prepareDataSocket_(final Socket socket) throws IOException {
        if (socket instanceof SSLSocket) {
            // Control socket is SSL
            final SSLSession session = ((SSLSocket) _socket_).getSession();
            final SSLSessionContext context = session.getSessionContext();
            context.setSessionCacheSize(0); // you might want to limit the cache
            try {
                final Field sessionHostPortCache = context.getClass()
                        .getDeclaredField("sessionHostPortCache");
                sessionHostPortCache.setAccessible(true);
                final Object cache = sessionHostPortCache.get(context);
                final Method method = cache.getClass().getDeclaredMethod("put", Object.class,
                        Object.class);
                method.setAccessible(true);
                String key = String.format("%s:%s", socket.getInetAddress().getHostName(),
                        String.valueOf(socket.getPort())).toLowerCase(Locale.ROOT);
                method.invoke(cache, key, session);
                key = String.format("%s:%s", socket.getInetAddress().getHostAddress(),
                        String.valueOf(socket.getPort())).toLowerCase(Locale.ROOT);
                method.invoke(cache, key, session);
            }
            catch (NoSuchFieldException e) {
                // Not running in expected JRE
                logger.warn("No field sessionHostPortCache in SSLSessionContext", e);
            }
            catch (Exception e) {
                // Not running in expected JRE
                logger.warn(e.getMessage());
            }
        }

    }

}