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 服务使用的文件目录。

  1. # 添加用户并指定密码
  2. adduser ftptest
  3. passwd ftptest
  4. # 创建一个供FTP服务使用的文件目录
  5. mkdir /var/ftp/test
  6. # 创建测试文件
  7. touch /var/ftp/test/testfile.txt
  8. # 运行以下命令更改/var/ftp/test目录的拥有者为ftptest
  9. chown -R ftptest:ftptest /var/ftp/test

6、修改 vsftpd.conf 配置文件,vim /etc/vsftpd/vsftpd.conf

  1. #除下面提及的参数,其他参数保持默认值即可。
  2. #修改下列参数的值:
  3. #禁止匿名登录FTP服务器。
  4. anonymous_enable=NO
  5. #允许本地用户登录FTP服务器。
  6. local_enable=YES
  7. #监听IPv4 sockets。
  8. listen=YES
  9. #在行首添加#注释掉以下参数:
  10. #关闭监听IPv6 sockets。
  11. #listen_ipv6=YES
  12. #在配置文件的末尾添加下列参数:
  13. #设置本地用户登录后所在目录。
  14. local_root=/var/ftp/test
  15. #全部用户被限制在主目录。
  16. chroot_local_user=YES
  17. #启用例外用户名单。
  18. chroot_list_enable=YES
  19. #指定例外用户列表文件,列表中用户不被锁定在主目录。
  20. chroot_list_file=/etc/vsftpd/chroot_list
  21. #开启被动模式。
  22. pasv_enable=YES
  23. allow_writeable_chroot=YES
  24. #本教程中为Linux实例的公网IP。
  25. pasv_address=<FTP服务器公网IP地址>
  26. #设置被动模式下,建立数据传输可使用的端口范围的最小值。
  27. #建议您把端口范围设置在一段比较高的范围内,例如50000~50010,有助于提高访问FTP服务器的安全性。
  28. pasv_min_port=<port number>
  29. #设置被动模式下,建立数据传输可使用的端口范围的最大值。
  30. pasv_max_port=<port number>

7、创建 chroot_list 文件,并在文件中写入例外用户名单,vim /etc/vsftpd/chroot_list

  1. ftptest

8、重启 vsftpd 服务,并开放 21 端口:systemctl restart vsftpd.service

1.2 客户端测试

打开本地文件管理器,在地址栏中输入 ftp://<FTP服务器公网IP地址>:FTP端口
image.png

2. 搭建SFTP服务器

参考链接:https://blog.csdn.net/d1240673769/article/details/106277897?spm=1001.2101.3001.6650.9&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-9.pc_relevant_antiscanv2&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-9.pc_relevant_antiscanv2&utm_relevant_index=13 使用环境:Centos7

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。

  1. mkdir /usr/sftppath
  2. chown root:sftp /usr/sftppath # 该路径所属者为root,sftp_user无权限进行写操作,只能读
  3. chmod 755 /usr/sftppath

5、创建该用户所属的数据路径。

  1. mkdir -p /usr/sftppath/sftpdata
  2. chown sftp_user:sftp /usr/sftppath/sftpdata # sftp用户在该路径下有读和写的权限,可进行创建和删除目录文件等操作
  3. chmod 755 /usr/sftppath/sftpdata

6、修改 /etc/ssh/sshd_config 的配置文件:

  1. #Subsystem sftp /usr/libexec/openssh/sftp-server
  2. Subsystem sftp internal-sftp
  3. # 最后一行新增
  4. Match Group sftpusers
  5. X11Forwarding no
  6. AllowTcpForwarding no
  7. ChrootDirectory /usr/sftppath # 设定属于用户组sftpusers的用户访问的根文件夹,sftp用户在根路径下只有读的权限
  8. 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);
        }
    }
}