環境:java JDK 1.八、org.apache.commons-net-3.6.jar、端口已放開java
FTPClient ftpClient = new FTPClient(protocol, false); ftpClient.setRemoteVerificationEnable(false); ftpClient.setControlKeepAliveTimeout(300); ftpClient.setDataTimeout(300); InputStream fin = null; try { ftpClient.connect(host, port); int reply = ftpClient.getReplyCode(); if (ftpClient.isPositiveComletion(reply)) { if (ftpClient.login(username, password)) { ftpClient.feat(); ftpClient.execPBSZ(0); ftpClient.execPROT("p"); ftpClient.setControlEncoding("UTF-8"); ftpClient.setFileType(ftpClient.BINARY_FILE_TYPE); ftpClient.enterLocalPassiveMode(); try { fin = new FileInoutStream(new File("C:\\doc_home\\test1.txt")); } catch (FileNotFoundException e) { System.out.println("---file not found"); } String remoteFile = "test1.txt"; ftpClient.mlsd(); if (ftpClient.storeFile(remoteFile, fin)) { fin.close(); } else { System.out.println("could not store file"); } fin.close(); } else { System.out.println("FTP login failed"); } } else { System.out.println("FTP connect to host failed"); } } catch (IOException ioe) { ioe.printStackTrace(); System.out.println("FTP client received network error"); } finally { if (fin != null) { try { fin.close(); } catch (IOException ioe) { //do nothing } } } 異常: javax.net.ssl.SSLHandshakeException:Remote host closed connection during handshake FTP client received network error at sun.security.ssl.SSLSocketImpl.readRecord(Unknown Source) at sun.security.ssl.SSLSocketImpl.performInitialHandshake(Unknown Source) at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source) at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source) at org.apache.commons.net.ftp.FTPSClient.openDataConnection(FTPClient.java:646) at org.apache.commons.net.ftp.FTPSClient.storeFile(FTPClient.java:653) at org.apache.commons.net.ftp.FTPSClient.storeFile(FTPClient.java:2030) at ibgdashboardtest.Demo4.main(Demo4.java:75) Caused by:java.io.EOFException:SSL peer shut down incorrectly at sun.security.ssl.InputRecord.read(Unknown Source) ... 9 more
解決方法:本身定義一個類繼承FTPSClient,重載_prepareDataSocket_(final Socket socket)方法,添加了TLS的session hash支持並擴展了密鑰,使用時用該類來替代FTPSClient的使用apache
import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.Socket; import java.util.Locale; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSessionContext; import javax.net.ssl.SSLSocket; import org.apache.commons.net.ftp.FTPSClient; public class SSLSessionReuseFTPSClient extends FTPSClient { // adapted from: // https://trac.cyberduck.io/browser/trunk/ftp/src/main/java/ch/cyberduck/core/ftp/FTPClient.java @Override protected void _prepareDataSocket_(final Socket socket) throws IOException { if (socket instanceof SSLSocket) { // Control socket is SSL final SSLSession session = ((SSLSocket) _socket_).getSession(); if (session.isValid()) { final SSLSessionContext context = session.getSessionContext(); 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); method.invoke(cache, String .format("%s:%s", socket.getInetAddress().getHostName(), String.valueOf(socket.getPort())) .toLowerCase(Locale.ROOT), session); method.invoke(cache, String .format("%s:%s", socket.getInetAddress().getHostAddress(), String.valueOf(socket.getPort())) .toLowerCase(Locale.ROOT), session); } catch (NoSuchFieldException e) { throw new IOException(e); } catch (Exception e) { throw new IOException(e); } } else { throw new IOException("Invalid SSL Session"); } } } }
若是出現兼容問題,應用程序可能會經過JDK中將系統設置屬性jdk.tls.useExtendedMasterSecret設置爲false來禁用此擴展,在JDK 1.8.0_161中設置:session
System.setProperty("jdk.tls.useExtendedMasterSecret", "false");