一、服务器安装
操作系统Ubuntu:
sudo apt-get install vsftpd
操作系统Centos:
sudo yum install -y vsftpd
##创建ftps用户,设置默认目录
useradd -d /home/ftp ftpname
passwd ftpuse123
##设置用户权限,不能登录
usermod -s /sbin/nologin ftpname
##修改用户默认目录
usermod -d /ftp ftpname
## 备份原始配置文件
sudo cp /etc/vsftpd.conf /etc/vsftpd.conf.bak
################ vsftpd.conf ################
listen=NO
listen_ipv6=YES
anonymous_enable=NO
local_enable=YES
write_enable=YES
anon_upload_enable=YES
anon_mkdir_write_enable=YES
local_umask=022
dirmessage_enable=YES
use_localtime=YES
xferlog_enable=YES
connect_from_port_20=YES
pam_service_name=ftp
idle_session_timeout=600
data_connection_timeout=120
listen_port=10021
pasv_promiscuous=YES
pasv_enable=YES
pasv_min_port=18700
pasv_max_port=18790
port_enable=YES
#日志文件
xferlog_file=/var/log/xferlog
#使用标准文件日志
xferlog_std_format=YES
#是否允许上传二进制文件
ascii_upload_enable=YES
#是否允许下载二进制文件
ascii_download_enable=YES
#是否启用SSL,默认NO
ssl_enable=YES
#是否激活sslv2加密,默认NO
ssl_sslv2=YES
#是否激活sslv3加密,默认NO
ssl_sslv3=YES
#是否激活tls v1加密,默认NO
ssl_tlsv1=YES
#非匿名用户登陆时是否加密
force_local_logins_ssl=YES
#非匿名用户传输数据时是否加密
force_local_data_ssl=YES
#ssl证书位置
rsa_cert_file=/etc/vsftpd/.sslkey/vsftpd.pem
################ vsftpd.conf ################
## 证书生成地址,可自定义,配置文件中对应即可
mkdir -p /etc/vsftpd/.sslkey
sudo openssl req -new -x509 -nodes -days 3650 -out vsftpd.pem -keyout vsftpd.pem
##按提示输入相关信息即可
服务启动/重启/停止
systemctl start/restart/stop vsftpd
1.2 centos的安装方式
sudo yum install -y vsftpd
## 服务和配置文件都在/etc/vsftpd下
和Ubuntu有点区别,以上操作完成,使用外部工具可能会连不上,需要修改
1、修改/etc/pam.d/vsftpd
添加:auth required pam_nologin.so
2、如果想要root也可以远程登录
修改 /etc/vsftpd/ftpusers ,注释掉第一行 root
二、代码案例
切记不要使用3.1,有坑
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.8.0</version>
</dependency>
@Slf4j
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
return completePendingCommand();
}
@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();
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
log.error("No field sessionHostPortCache in SSLSessionContext");
} catch (Exception e) {
// Not running in expected JRE
e.printStackTrace();
}
}
}
}
@Slf4j
public class FtpsUtils {
private static SSLSessionReuseFtpsClient ftpsClient = null;
public static final String PATH_SUFFIX = "/";
/**
* 登录认证
*
* @return true/false
*/
public static 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");
FtpsProperties ftpsProperties = BeanUtil.getBean(FtpsProperties.class);
ftpsClient.connect(ip, port);
ftpsClient.login(username, password);
int reply = ftpsClient.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftpsClient.disconnect();
return false;
}
// 主动模式,PORT模式。通知客户端打开一个数据端口,服务端将连接到这个端口进行数据传输。
// ftpsClient.enterRemotePassiveMode();
// 被动模式,PASV模式。通知服务器打开一个数据端口,客户端将连接到这个端口进行数据传输。
ftpsClient.enterLocalPassiveMode();
// 流传输模式
ftpsClient.setFileTransferMode(FTP.STREAM_TRANSFER_MODE);
// 数据连接超时(以毫秒为单位),vsftpd配置文件默认120s
ftpsClient.setDataTimeout(18000);
// 数据通道保护级别
ftpsClient.execPROT("P");
ftpsClient.setFileType(FTP.BINARY_FILE_TYPE);
ftpsClient.setBufferSize(1024);
log.info("ftps连接成功....ip={},port={}", ip, port);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 上传文件
*
* @param directory 服务端路径
* @param file 本地文件路径
*/
public static boolean uploadFile(String directory, File file) {
try {
if (createDirectory(directory)) {
ftpsClient.storeFile(file.getName(), new FileInputStream(file));
ftpsClient.logout();
log.info("附件上传成功!远程文件路径:{}", directory);
return true;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ftpsClient.isConnected()) {
try {
ftpsClient.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return false;
}
/**
* 创建多层目录文件,如果有ftp服务器已存在该文件,则不创建,如果无,则创建
*
* @param remote 目录
*/
public static boolean createDirectory(String remote) {
String directory = remote;
try {
if (!remote.endsWith(PATH_SUFFIX)) {
directory = directory + PATH_SUFFIX;
}
// 如果远程目录不存在,则递归创建远程服务器目录
if (!PATH_SUFFIX.equals(directory) && !ftpsClient.changeWorkingDirectory(directory)) {
int start;
int end;
if (directory.startsWith(PATH_SUFFIX)) {
start = 1;
} else {
start = 0;
}
end = directory.indexOf(PATH_SUFFIX, start);
StringBuilder path = new StringBuilder();
StringBuilder paths = new StringBuilder();
do {
String subDirectory = remote.substring(start, end);
path.append(path).append(PATH_SUFFIX).append(subDirectory);
// 目录不存在就创建
if (!ftpsClient.changeWorkingDirectory(subDirectory)) {
if (ftpsClient.makeDirectory(subDirectory)) {
ftpsClient.changeWorkingDirectory(subDirectory);
}
}
paths.append(paths).append(PATH_SUFFIX).append(subDirectory);
start = end + 1;
end = directory.indexOf(PATH_SUFFIX, start);
// 检查所有目录是否创建完毕
} while (end > start);
}
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 下载文件
*
* @param directory 服务端路径
* @param file 本地文件路径
*/
public static boolean downloadFile(String directory, File file) {
FileOutputStream fos = null;
try {
String[] listNames = ftpsClient.listNames(directory);
for (String path : listNames) {
if (path.endsWith(file.getName())) {
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
fos = new FileOutputStream(file);
ftpsClient.retrieveFile(path, fos);
log.info("文件下载成功,本地保存路径:{}", file.getAbsolutePath());
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;
}
}
public class FTPTest {
public static void main(String[] args) {
String host = "192.168.0.1";
int port = 10021;
String userName = "ftp01";
String passWord = "ftp2022";
FtpsUtils.login(host, port, userName, passWord);
String directory = new SimpleDateFormat("yyyyMMddHH").format(new Date());
File file = new File("/Users/Documents/Dockerfile");
FtpsUtils.uploadFile(directory, file);
FtpsUtils.login(host, port, userName, passWord);
File uploadFilePath = new File("/Users/Desktop/ftpDemo/Dockerfile");
FtpsUtils.downloadFile(directory, uploadFilePath);
}
}