某些服务器会默认开启TLS会话恢复,如FileZilla Server 1.0及以后的版本(相对于1.0以前版本就是先当与勾选了Require TLS session resumption on data connect when using PORT P
)。Apache Commons Net
目前是不支持TLS会话恢复的,所以我们只能通过重写FTPSClient来实现。不然你就会经常看到报错如:425 Unable to build data connection: TLS session of data connection not resumed.
package net.evecom.iaplatform.common.ftp;
import org.apache.commons.net.ftp.FTPSClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.SSLSocket;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.Socket;
import java.util.Locale;
/**
* <p>
* <B>Description: 共享SSLSession FTPS客户端</B>
* </P>
*
* @author Ryan Huang
* @version 1.0
*/
public class SharedSSLFTPSClient extends FTPSClient {
public SharedSSLFTPSClient(String protocol, boolean isImplicit) {
super(protocol, isImplicit);
}
private static final Logger logger = LoggerFactory.getLogger(SharedSSLFTPSClient.class);
@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.ENGLISH);
method.invoke(cache, key, session);
key = String.format("%s:%s", socket.getInetAddress().getHostAddress(),
String.valueOf(socket.getPort())).toLowerCase(Locale.ENGLISH);
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());
}
}
}
}
注意:对于JDK1.8,使用上述方法即可解决。但是如果使用JDK11,需要在连接前指定协议ftpsClient.setEnabledProtocols(new String[]{"TLSv1.2"});
。因为JDK11默认使用TLSv1.3
,但是目前TLSv1.3
不支持共享SSLSession.
参考地址:https://docs.spring.io/spring-integration/reference/ftp/advanced-configuration.html