跳到主要内容

高级配置

QWen Plus 中英对照 Advanced Configuration

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

/**
* 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
}
java

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

public class AdvancedFtpSessionFactory extends DefaultFtpSessionFactory {

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

FTPS 和共享 SSLSession

当使用 FTP over SSL 或 TLS 时,一些服务器要求在控制和数据连接上使用相同的 SSLSession 。这是为了防止“窃取”数据连接。更多信息请参见 scarybeastsecurity.blogspot.cz/2009/02/vsftpd-210-released.html

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

以下解决方案,来自 Stack Overflow,使用了对 sun.security.ssl.SSLSessionContextImpl 的反射,因此可能无法在其他 JVM 上工作。该栈溢出答案提交于 2015 年,且该解决方案已由 Spring Integration 团队在 JDK 1.8.0_112 上测试过。

以下示例显示如何创建一个 FTPS 会话:

@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());
}
}

}

}
java