1. 搭建FTP服务器
1.1 安装配置Vsftpd
参考链接:https://help.aliyun.com/document_detail/92048.html#section-821-887-8np 使用环境:Centos7
1、安装 vsftpd:yum install -y vsftpd
。
2、设置 ftp 服务开机启动:systemctl enable vsftpd.service
。
3、启动 ftp 服务:systemctl start vsftpd.service
。
4、查看 ftp 服务监听端口,默认为 21:netstat -antup | grep ftp
。
5、为 ftp 服务创建一个用户,并指定供 ftp 服务使用的文件目录。
# 添加用户并指定密码
adduser ftptest
passwd ftptest
# 创建一个供FTP服务使用的文件目录
mkdir /var/ftp/test
# 创建测试文件
touch /var/ftp/test/testfile.txt
# 运行以下命令更改/var/ftp/test目录的拥有者为ftptest
chown -R ftptest:ftptest /var/ftp/test
6、修改 vsftpd.conf 配置文件,vim /etc/vsftpd/vsftpd.conf
#除下面提及的参数,其他参数保持默认值即可。
#修改下列参数的值:
#禁止匿名登录FTP服务器。
anonymous_enable=NO
#允许本地用户登录FTP服务器。
local_enable=YES
#监听IPv4 sockets。
listen=YES
#在行首添加#注释掉以下参数:
#关闭监听IPv6 sockets。
#listen_ipv6=YES
#在配置文件的末尾添加下列参数:
#设置本地用户登录后所在目录。
local_root=/var/ftp/test
#全部用户被限制在主目录。
chroot_local_user=YES
#启用例外用户名单。
chroot_list_enable=YES
#指定例外用户列表文件,列表中用户不被锁定在主目录。
chroot_list_file=/etc/vsftpd/chroot_list
#开启被动模式。
pasv_enable=YES
allow_writeable_chroot=YES
#本教程中为Linux实例的公网IP。
pasv_address=<FTP服务器公网IP地址>
#设置被动模式下,建立数据传输可使用的端口范围的最小值。
#建议您把端口范围设置在一段比较高的范围内,例如50000~50010,有助于提高访问FTP服务器的安全性。
pasv_min_port=<port number>
#设置被动模式下,建立数据传输可使用的端口范围的最大值。
pasv_max_port=<port number>
7、创建 chroot_list 文件,并在文件中写入例外用户名单,vim /etc/vsftpd/chroot_list
ftptest
8、重启 vsftpd 服务,并开放 21 端口:systemctl restart vsftpd.service
。
1.2 客户端测试
打开本地文件管理器,在地址栏中输入 ftp://<FTP服务器公网IP地址>:FTP端口
。
2. 搭建SFTP服务器
2.1 了解SFTP
SFTP 同样是使用加密传输认证信息和传输的数据,所以,使用 SFTP 是非常安全的。但是,由于这种传输方式使用了加密/解密技术,所以传输效率比普通的 FTP 要低得多,如果您对网络安全性要求更高时,可以使用 SFTP 代替 FTP。
2.2 配置SFTP
1、创建 sftp 用户组,组名为 sftpusers:groupadd sftpusers
。
2、创建 sftp_user 用户,并设置为 sftp 组:useradd -g sftpusers -s /sbin/nologin -M sftp_user
。
3、修改 sftp_user 用户密码。
4、创建 sftp_user 用户的根目录 (/usr/sftppath) 和属主属组,修改权限为 755。
mkdir /usr/sftppath
chown root:sftp /usr/sftppath # 该路径所属者为root,sftp_user无权限进行写操作,只能读
chmod 755 /usr/sftppath
5、创建该用户所属的数据路径。
mkdir -p /usr/sftppath/sftpdata
chown sftp_user:sftp /usr/sftppath/sftpdata # sftp用户在该路径下有读和写的权限,可进行创建和删除目录文件等操作
chmod 755 /usr/sftppath/sftpdata
6、修改 /etc/ssh/sshd_config 的配置文件:
#Subsystem sftp /usr/libexec/openssh/sftp-server
Subsystem sftp internal-sftp
# 最后一行新增
Match Group sftpusers
X11Forwarding no
AllowTcpForwarding no
ChrootDirectory /usr/sftppath # 设定属于用户组sftpusers的用户访问的根文件夹,sftp用户在根路径下只有读的权限
ForceCommand internal-sftp
7、配置完成后,重启 sshd:systemctl restart sshd
。
8、sftp 登录:sftp sftp_user@127.0.0.1
。
3. Java连接SFTP服务器
public class SftpUtil {
protected Logger logger = LoggerFactory.getLogger(SftpUtil.class);
private String host;
private int port;
private String username;
private String password;
private ChannelSftp sftp;
private Session session;
private int timeout = 5000;
public SftpUtil(String host, int port, String username, String password) {
this.host = host;
this.port = port;
this.username = username;
this.password = password;
}
/**
* 连接SFTP服务器
*
* @throws Exception
*/
public void connect() throws Exception {
JSch jsch = new JSch();
try {
session = jsch.getSession(username, host, port);
if (Objects.nonNull(password)) {
session.setPassword(password);
}
session.setConfig("PreferredAuthentications", "password");
session.setConfig("StrictHostKeyChecking", "no");
session.setTimeout(timeout);
session.connect();
// 打开SFTP通道
sftp = (ChannelSftp) session.openChannel("sftp");
// 建立连接
sftp.connect();
} catch (JSchException e) {
logger.error("FTP服务器连接异常!", e);
throw e;
}
}
/**
* 关闭SFTP连接
*/
public void disconnect() {
if (sftp != null) {
if (sftp.isConnected())
sftp.disconnect();
}
if (session != null) {
if (session.isConnected())
session.disconnect();
}
}
/**
* 上传文件
*
* @param remoteDir
* @param filePaths
* @throws Exception
*/
public void uploadFiles(String remoteDir, List<Path> filePaths) throws Exception {
try {
if (Objects.isNull(sftp.ls(remoteDir))) {
sftp.mkdir(remoteDir);
}
sftp.cd(remoteDir);
} catch (SftpException e) {
throw e;
}
FileInputStream fis = null;
for (Path filePath : filePaths) {
File file = new File(filePath.toString());
fis = new FileInputStream(file);
sftp.put(fis, file.getName());
fis.close();
}
}
/**
* 上传文件
*
* @param remoteDir
* @param localDir
* @param fileNames
* @throws Exception
*/
public void uploadFiles(String remoteDir, String localDir, List<String> fileNames) throws Exception {
if (!localDir.endsWith(File.separator)) {
localDir += File.separator;
}
List<Path> filePaths = new ArrayList<>();
for (String fileName : fileNames) {
Path path = Paths.get(localDir, fileName);
filePaths.add(path);
}
this.uploadFiles(remoteDir, filePaths);
}
/**
* 上传文件
*
* @param remoteDir
* @param localDir
* @param fileName
* @throws Exception
*/
public void uploadFile(String remoteDir, String localDir, String fileName) throws Exception {
if (!localDir.endsWith(File.separator)) {
localDir += File.separator;
}
List<Path> filePaths = new ArrayList<>();
filePaths.add(Paths.get(localDir, fileName));
this.uploadFiles(remoteDir, filePaths);
}
/**
* 查询SFTP指定目录
*
* @param remoteDir
* @return
*/
public Vector<?> listFiles(String remoteDir) throws SftpException {
try {
Vector<?> vector = sftp.ls(remoteDir);
if (Objects.nonNull(vector)) {
// 移除"."和".."
vector.remove(0);
vector.remove(0);
return vector;
}
} catch (SftpException e) {
throw e;
}
return null;
}
/**
* 删除指定文件
*
* @param remoteDir
* @param fileName
* @throws SftpException
*/
public void deleteFile(String remoteDir, String fileName) throws SftpException {
try {
sftp.cd(remoteDir);
sftp.rm(fileName);
} catch (SftpException e) {
throw e;
}
}
/**
* 判断指定目录是否存在
*
* @param directory
* @return
*/
public boolean isDir(String directory) {
boolean isDir = false;
try {
SftpATTRS sftpAttrs = sftp.lstat(directory);
return sftpAttrs.isDir();
} catch (Exception e) {
isDir = false;
}
return isDir;
}
/**
* 清空指定目录
*
* @param remoteDir
* @throws SftpException
*/
public void cleanDir(String remoteDir) throws SftpException {
Vector<?> vector = this.listFiles(remoteDir);
if (Objects.nonNull(vector)) {
sftp.cd(remoteDir);
for (Object o : vector) {
ChannelSftp.LsEntry lsEntry = (ChannelSftp.LsEntry) o;
try {
String filename = lsEntry.getFilename();
if (this.isDir(filename)) {
// 递归清除子目录下的文件
cleanDir(filename);
// 删除目录
sftp.rmdir(filename);
} else {
sftp.rm(filename);
}
} catch (SftpException e) {
throw e;
}
}
}
}
/**
* 下载指定目录下所有文件
*
* @param remoteDir
* @param localDir
* @param isClean
* @throws SftpException
*/
public List<Path> downloadFiles(String remoteDir, String localDir, boolean isClean) throws Exception {
List<Path> filePaths = new ArrayList<Path>();
Vector<?> vector = this.listFiles(remoteDir);
if (Objects.nonNull(vector)) {
// 检查指定的本地目录是否存在
File dir = new File(localDir);
if (!dir.exists() && !dir.mkdirs()) {
throw new Exception("创建本地目录失败!");
}
if (!localDir.endsWith(File.separator)) {
localDir += File.separator;
}
// 下载文件
sftp.cd(remoteDir);
String fileName = null;
FileOutputStream fos = null;
for (Object o : vector) {
ChannelSftp.LsEntry lsEntry = (ChannelSftp.LsEntry) o;
try {
fileName = lsEntry.getFilename();
if (this.isDir(fileName)) {
continue;
}
fos = new FileOutputStream(localDir + fileName);
// 下载
sftp.get(fileName, fos);
fos.close();
// 记录下载成功的文件
filePaths.add(Paths.get(localDir, fileName));
// 删除
if (isClean) {
sftp.rm(lsEntry.getFilename());
}
} catch (SftpException e) {
if (fos != null) {
fos.close();
}
// 下载失败,若存在空文件则删除
File file = new File(localDir + fileName);
if (file.exists()) {
file.delete();
}
throw new Exception("下载文件 " + fileName + " 失败," + e.getMessage());
}
}
}
return filePaths;
}
/**
* 下载文件
*
* @param remoteDir
* @param fileNames
* @param localDir
* @throws Exception
*/
public List<Path> downloadFiles(String remoteDir, List<String> fileNames, String localDir) throws Exception {
List<Path> filePaths = new ArrayList<Path>();
// 检查指定的本地目录是否存在
File dir = new File(localDir);
if (!dir.exists() && !dir.mkdirs()) {
throw new Exception("创建本地目录失败!");
}
if (!localDir.endsWith(File.separator)) {
localDir += File.separator;
}
// 下载指定文件
if (Objects.nonNull(remoteDir)) {
sftp.cd(remoteDir);
}
for (String fileName : fileNames) {
FileOutputStream fos = new FileOutputStream(localDir + fileName);
try {
sftp.get(fileName, fos);
fos.close();
filePaths.add(Paths.get(localDir, fileName));
} catch (SftpException e) {
if (fos != null) {
fos.close();
}
// 2: No such file
if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
// 下载失败,若存在空文件则删除
File file = new File(localDir + fileName);
if (file.exists()) {
file.delete();
}
continue;
} else {
throw e;
}
}
}
return filePaths;
}
/**
* 下载文件
*
* @param remoteDir
* @param fileName
* @param localDir
* @throws Exception
*/
public Path downloadFile(String remoteDir, String fileName, String localDir) throws Exception {
List<String> fileNames = new ArrayList<>();
fileNames.add(fileName);
List<Path> paths = this.downloadFiles(remoteDir, fileNames, localDir);
if (paths.isEmpty()) {
return null;
} else {
return paths.get(0);
}
}
}