网上一大把,测试不能用,谷歌了一下,发现少配置了一个环境变量。
System.setProperty("jdk.tls.useExtendedMasterSecret", "false");//设置环境变量
commons-net
commons-net
3.6
ftps的require_ssl_reuse配置为YES,所有SSL数据连接都需要显示SSL会话重用,需要重写FTPSClient。
package ftps;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
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;
import org.apache.commons.net.io.CopyStreamEvent;
import org.apache.commons.net.io.Util;
/*
使用FTPSClient连接FTP下载文件,连接和登录都没有问题,
但是下载文件时方法ftpsClient.listNames却报错:522 SSL connection failed; session reuse required: see require_ssl_reuse option in vsftpd.conf man page。
原来是FTP的require_ssl_reuse=YES导致的, 当选项require_ssl_reuse设置为YES时,所有SSL数据连接都需要显示SSL会话重用;
证明他们知道与控制信道相同的主秘钥。联系客户把这个参数设置成NO之后果然可以下载了。
如果设置为YES就没办法了吗,当然有办法,那就是使用SSL通道重用。
但FTPSClient目前是不支持ssl通道重用的,So,不要在浪费时间了。
不过可以重写FTPSClient达到目的。重写代码如下:
*/
public class SSLSessionReuseFTPSClient extends FTPSClient {
/**
* @param command the command to get
* @param remote the remote file name
* @param local the local file name
* @return true if successful
* @throws IOException on error
* @since 3.1
*/
@Override
protected boolean _retrieveFile(String command, String remote, OutputStream local) throws IOException {
Socket socket = _openDataConnection_(command, remote);
if (socket == null) {
return false;
}
final InputStream input;
input = new BufferedInputStream(socket.getInputStream());
// Treat everything else as binary for now
try {
Util.copyStream(input, local, getBufferSize(), CopyStreamEvent.UNKNOWN_STREAM_SIZE, null, false);
} finally {
Util.closeQuietly(input);
Util.closeQuietly(socket);
}
// Get the transfer response
boolean ok = completePendingCommand();
return ok;
}
@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(preferences.getInteger("ftp.ssl.session.cache.size"));
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);
final String key = String.format("%s:%s", socket.getInetAddress().getHostName(), String.valueOf(socket.getPort())).toLowerCase(Locale.ROOT);
method.invoke(cache, key, session);
} catch (NoSuchFieldException e) {
e.printStackTrace();
// Not running in expected JRE
System.out.println("No field sessionHostPortCache in SSLSessionContext");
} catch (Exception e) {
// Not running in expected JRE
e.printStackTrace();
}
}
}
}
注意ftp服务器是用的主动模式,还是被动模式。
ftpsClient.enterRemotePassiveMode();// 主动模式,PORT模式。
ftpsClient.enterLocalPassiveMode();// 被动模式,PASV模式。
package ftps;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import org.apache.commons.net.PrintCommandListener;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClientConfig;
import org.apache.commons.net.ftp.FTPReply;
public class FTPSUtil {
private SSLSessionReuseFTPSClient ftpsClient = null;
/**
* 登陆
* @param ip
* @param port
* @param username
* @param password
* @date 2021年11月12日 下午5:03:14
*/
public boolean login(String ip, int port, String username, String password) {
try {
System.setProperty("jdk.tls.useExtendedMasterSecret", "false");//设置环境变量
ftpsClient = new SSLSessionReuseFTPSClient();
FTPClientConfig config = new FTPClientConfig();
ftpsClient.configure(config);
ftpsClient.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out)));// 打印调用日志,调试时使用,上线后注释
ftpsClient.setControlEncoding("utf-8");// 编码设置必须放在connect前不然不起作用
ftpsClient.connect(ip);
ftpsClient.login(username, password);
System.out.println("connecting...ftps服务器:" + ip + ":" + port);
int reply = ftpsClient.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftpsClient.disconnect();
return false;
}
// ftpsClient.enterRemotePassiveMode();// 主动模式,PORT模式。通知客户端打开一个数据端口,服务端将连接到这个端口进行数据传输。
ftpsClient.enterLocalPassiveMode();// 被动模式,PASV模式。通知服务器打开一个数据端口,客户端将连接到这个端口进行数据传输。
ftpsClient.setFileTransferMode(FTP.STREAM_TRANSFER_MODE);// 流传输模式
ftpsClient.setDataTimeout(18000);// 数据连接超时(以毫秒为单位)
ftpsClient.execPROT("P");// 数据通道保护级别
ftpsClient.setFileType(FTP.BINARY_FILE_TYPE);
ftpsClient.setBufferSize(1024);
System.out.println("连接成功...ftp服务器:" + ip + ":" + port);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 上传文件
* @param directory 服务端路径
* @param file 本地文件路径
* @date 2021年11月12日 下午5:03:14
*/
public boolean uploadFile(String directory, File file) {
try {
createDirectory(directory);
ftpsClient.storeFile(file.getName(), new FileInputStream(file));
ftpsClient.logout();
return true;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ftpsClient.isConnected()) {
try {
ftpsClient.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return false;
}
/**
* 创建多层目录文件,如果有ftp服务器已存在该文件,则不创建,如果无,则创建
* @param remote 目录
* @date 2021年11月12日 下午5:03:14
*/
public boolean createDirectory(String remote) {
String directory = remote;
try {
if (!remote.endsWith("/")) {
directory = directory + "/";
}
// 如果远程目录不存在,则递归创建远程服务器目录
if (!directory.equals("/") && !ftpsClient.changeWorkingDirectory(directory)) {
int start = 0;
int end = 0;
if (directory.startsWith("/")) {
start = 1;
} else {
start = 0;
}
end = directory.indexOf("/", start);
String paths = "", path = "";
while (true) {
String subDirectory = remote.substring(start, end);
path = path + "/" + subDirectory;
// 目录不存在就创建
if (!ftpsClient.changeWorkingDirectory(subDirectory)) {
if (ftpsClient.makeDirectory(subDirectory)) {
ftpsClient.changeWorkingDirectory(subDirectory);
}
}
paths = paths + "/" + subDirectory;
start = end + 1;
end = directory.indexOf("/", start);
// 检查所有目录是否创建完毕
if (end <= start) {
break;
}
}
}
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 下载文件
* @param directory 服务端路径
* @param file 本地文件路径
* @date 2021年11月12日 下午5:03:14
*/
public boolean downloadFile(String directory, File file) {
FileOutputStream fos = null;
try {
// ftpFiles ftpFiles = ftpsClient.listFiles(ftpsClient.printWorkingDirectory());
// FTPFile[] ftpFiles = ftpsClient.listFiles(directory);
String[] listNames = ftpsClient.listNames(directory);
for (String path : listNames) {
if (path.endsWith(file.getName())) {
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
System.out.println("本地保存路径:" + file.getAbsolutePath());
fos = new FileOutputStream(file);
ftpsClient.retrieveFile(path, fos);
System.out.println("下载文件成功");
return true;
}
}
ftpsClient.logout();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (ftpsClient.isConnected()) {
try {
ftpsClient.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return false;
}
}
package ftps;
import java.io.File;
public class Test {
public static void main(String[] args) {
String host = "192.168.5.200";
int port = 21;
String userName = "bossftps";
String passWord = "123456";
FTPSUtil ftps = new FTPSUtil();
ftps.login(host, port, userName, passWord);
String directory = "/data/目录1/目录2";
File file = new File("c:/test/你好.txt");
ftps.uploadFile(directory, file);
ftps.login(host, port, userName, passWord);
directory = "/data/目录1/目录2/你好.txt";
ftps.downloadFile(directory, file);
}
}