高階配置
DefaultFtpSessionFactory 提供了一個對底層客戶端 API 的抽象,該 API(自 Spring Integration 2.0 起)是 Apache Commons Net。這使您免於 org.apache.commons.net.ftp.FTPClient 的低階配置細節。會話工廠上暴露了一些常見屬性(自版本 4.0 起,現在包括 connectTimeout、defaultTimeout 和 dataTimeout)。然而,有時您需要訪問更低階的 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
}
如您所見,這兩個方法沒有預設實現。但是,透過擴充套件 DefaultFtpSessionFactory,您可以覆蓋這些方法以提供更高階的 FTPClient 配置,如下例所示
public class AdvancedFtpSessionFactory extends DefaultFtpSessionFactory {
protected void postProcessClientBeforeConnect(FTPClient ftpClient) throws IOException {
ftpClient.setActivePortRange(4000, 5000);
}
}
FTPS 和共享 SSLSession
當使用基於 SSL 或 TLS 的 FTP 時,某些伺服器要求在控制連線和資料連線上使用相同的 SSLSession。這是為了防止“竊取”資料連線。有關更多資訊,請參閱 scarybeastsecurity.blogspot.cz/2009/02/vsftpd-210-released.html。
目前,Apache FTPSClient 不支援此功能。請參閱 NET-408。
以下解決方案(由 Stack Overflow 提供)使用對 sun.security.ssl.SSLSessionContextImpl 的反射,因此可能無法在其他 JVM 上工作。Stack Overflow 的答案於 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());
}
}
}
}