Linux服务器下安装vsftpd,搭建文件服务器

前言

本次搭建文件服务器选用的是Linux系统,CentOS版本

Linux操作命令介绍 

这里也简单介绍下本次安装会用到的Linux操作命令。

rpm -qa  :rpm是redhat package manager的简写,是Red Hat Linux发行版专门用来管理Linux各项套件的程序,由于它遵循 GPL 规则且功能强大方便,因而广受欢迎。逐渐受到其他发行版的采用。-q命令指使用询问模式,当遇到任何问题时,rpm指令会先询问用户。-a命令指查询所有套件。

grep  :用于查找文件里符合条件的字符串,查找内容包含指定范本样式的文件,如果发现某文件的内容符合所指定的范本样式,预设grep指令会把含有范本样式的那一列显示出来。

|  :该符号代表管道流,用法是:command1 | command2,表示将第一个命令的标准输出作为第二个命令的标准输入

ps  :ps命令用于显示当前进程 (process) 的状态。-aux命令指显示所有包含其他使用者的行程。

yum  : Yellow dog Updater, Modified,是一个在Fedora和RedHat以及SUSE中的Shell前端软件包管理器。基于rmp包管理器,能从指定的服务器下载rpm包,处理好对应的依赖关系,不用我们自己查找对应的依赖,一一下载。-y命令指当安装过程中有提示全部选择为"yes"

安装vsftpd 

1、检查是否安装vsftpd

rpm -qa | grep vsftpd

输入上述命令,查询我们的Linux系统是否已经安装vsftpd,如果什么也没有,则说明我们还未安装vsftpd,已经安装了,会显示对应的vsftpd版本信息

2、安装vsftpd

yum -y install vsftpd

输入上述命令,安装vsftpd,安装成功会提示以下信息

3、启动vsftpd服务

service vsftpd start

输入上述命令,启动vsftpd服务

vsftpd对应的停止命令

service vsftpd stop

vsftpd对应的重启命令

service vsftpd restart

 

4、查询vsftpd进程

ps aux|grep vsftpd

输入上述命令,查询我们的系统进程中是否存在vsftpd服务

创建ftp用户

# 创建一个ftp文件夹,用于文件的上传和下载,位置自行定义
cd /usr/local
mkdir ftpFile
cd ftpFile
mkdir risk
# 在Linux上创建一个用户,专门用于ftp文件上传和下载
useradd spirit
# 限制用户spirit只能通过FTP访问服务器,而不能直接登录服务器
usermod -s /sbin/nologin spirit
# 将上面创建的文件夹授权给spirit用户
usermod -d /usr/local/ftpFile/risk spirit
# 再将risk文件夹的用户组更改为spirit
chown spirit:spirit risk
# 并且将risk文件夹的权限修改为用户组内可读可写可执行
chmod 775 risk
# 最后重置spirit的密码,重复两次输入后完成
passwd spirit

这里可能会遇到的问题:

       我们创建用户的时候,使用了nologin的shell,但是ftp用户无法登录,这里把PAM模块对vsftp登录的过度验证注释掉就可以了,截图中auth       required pam_shells.so这一行,或者是将pam_shells.so改为pam_nologin.so

       risk文件夹的用户组更改成功后,是下面的状态

配置vsftpd

vsftpd的主配置文件 vsftpd.conf 默认在 /etc/vsftpd 文件夹下,下面的配置文件是限定上述创建的spirit用户采用被动模式访问的

# 关闭匿名访问,不允许未授权用户访问ftp服务
anonymous_enable=NO

# 开启上传下载文件日志
xferlog_enable=YES
# 上传下载文件日志所在位置
xferlog_file=/var/log/xferlog

...

pam_service_name=vsftpd
# 是否允许ftpusers文件中的用户登录FTP服务器,默认为NO
# 若此项设为YES,则user_list文件中的用户允许登录FTP服务器
# 而如果同时设置了userlist_deny=YES,则user_list文件中的用户将不允许登录FTP服务器,甚至连输入密码提示信息都没有
userlist_enable=YES
# 设置是否阻扯user_list文件中的用户登录FTP服务器,默认为YES
userlist_deny=NO
# user_list文件所在位置
userlist_file=/etc/vsftpd/user_list
tcp_wrappers=YES

allow_writeable_chroot=YES

# ftp默认文件路径
local_root=/usr/local/ftpFile/risk
# 是否将所有用户限制在主目录,YES为启用,NO禁用.(该项默认值是NO,即在安装vsftpd后不做配置的话,ftp用户是可以向上切换到目录之外的)
chroot_local_user=YES
# 是否启动限制用户的名单,YES为启用,NO禁用(包括注释掉也为禁用)
chroot_list_enable=YES
# chroot_list文件所在位置
chroot_list_file=/etc/vsftpd/chroot_list

# 是否开启被动模式
pasv_enable=YES
# 被动模式端口范围
pasv_min_port=40000
pasv_max_port=41000

# 是否开启ftp操作日志
dual_log_enable=YES
# ftp操作日志所在位置
vsftpd_log_file=/var/log/vsftpd.log

/etc/vsftpd文件夹下默认没有chroot_list文件的,我们手动创建一个

cd /etc/vsftpd
# 创建文件chroot_list
vim chroot_list
# 添加用户:spirit,保存退出,再在user_list文件中也加入spirt用户,一个用户一行
vim /etc/selinux/config
# 修改SELINUX=disabled,也可以用下面的命令执行
setenforce 0

保存完配置文件后,重启vsftpd服务

service vsftpd restart

可能会遇到的问题:

1、chroot_local_user与chroot_list_enable的区别,可以参考下面这篇博客

https://blog.csdn.net/bluishglc/article/details/42398811 

2、VSFTPD一直提示“用户身份验证失败”:搭建好之后,当日都正常,自从第二天服务器被重启之后,无论是xftp还是java客户端登录就一直提示“用户身份验证失败”,无论是匿名还是使用root账户或者ftpadmin账户均如此

这个问题发生在最新的这是由于下面的更新造成的:

- Add stronger checks for the configuration error of running with a writeable root directory inside a chroot(). 

This may bite people who carelessly turned on chroot_local_user but such is life.

从2.3.5之后,vsftpd增强了安全检查,如果用户被限定在了其主目录下,则该用户的主目录不能再具有写权限了!如果检查发现还有写权限,就会报该错误。

 要修复这个错误,可以用命令chmod a-w /home/user去除用户主目录的写权限,注意把目录替换成你自己的。或者你可以在vsftpd的配置文件中增加下列两项中的一项:

allow_writeable_chroot=YES

防火墙配置

如果启用了防火墙,需要开放上述配置的端口范围

vim /etc/sysconfig/iptables
# 添加vsftpd的端口配置
-A INPUT -p TCP --dport 40000:41000 -j ACCEPT
-A OUTPUT -p TCP --sport 40000:41000 -j ACCEPT

配置完成后,重启防火墙

firewall-cmd --reload

测试是否能访问

在浏览器输入ftp:// + ip就能访问到我们的ftp服务器,输入前面创建的帐号密码,就能查看内部文件

基于java的ftpClient访问Linux的vsftpd服务,实现文件上传和下载

ftpClient辅助类

package com.mine.risk.util;

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.UnknownHostException;

/**
 * 文件传输辅助工具类
 * @author spirit
 * @version 1.0
 * @date 2019-06-26 17:05
 */
public class FtpUtil {

    private FTPClient ftp;

    public FtpUtil() {
        ftp = new FTPClient();
        //解决上传文件时文件名乱码
        ftp.setControlEncoding("UTF-8");
    }

    public FtpUtil(String controlEncoding) {
        ftp = new FTPClient();
        // 解决上传文件时文件名乱码
        ftp.setControlEncoding(controlEncoding);
    }

    /**
     * Connect to FTP server.
     * @param host FTP server address or name
     * @param port FTP server port
     * @param user user name
     * @param password user password
     * @throws IOException on I/O errors
     */
    public void connect(String host, int port, String user, String password) throws IOException {
        // Connect to server.
        try {
            ftp.connect(host, port);
        } catch (UnknownHostException ex) {
            throw new IOException("Can't find FTP server '" + host + "'");
        }
        // Check response after connection attempt.
        int reply = ftp.getReplyCode();
        if (!FTPReply.isPositiveCompletion(reply)) {
            disconnect();
            throw new IOException("Can't connect to server '" + host + "'");
        }
        if ("".equals(user)) {
            user = "anonymous";
        }
        // Login.
        if (!ftp.login(user, password)) {
            disconnect();
            throw new IOException("Can't login to server '" + host + "'");
        }
        // Set data transfer mode.
        ftp.setFileType(FTP.BINARY_FILE_TYPE);
        // Use passive mode to pass firewalls.
        ftp.enterLocalPassiveMode();
    }

    /**
     * Disconnect from the FTP server
     */
    public void disconnect(){
        if (ftp.isConnected()) {
            try {
                ftp.logout();
                ftp.disconnect();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 创建文件夹并切换到改目录下
     * @param basePath 基础目录路径
     * @param filePath 待创建文件目录路径
     * @throws Exception on errors
     */
    public void makeDir(String basePath, String filePath) throws Exception{
        try {
            //切换到上传目录
            if (!ftp.changeWorkingDirectory(basePath+filePath)) {
                //如果目录不存在创建目录
                String[] dirs = filePath.split("/");
                String tempPath = basePath;
                for (String dir : dirs) {
                    if (null == dir || "".equals(dir)) {
                        continue;
                    }
                    tempPath += "/" + dir;
                    if (!ftp.changeWorkingDirectory(tempPath)) {
                        if (!ftp.makeDirectory(tempPath)) {
                            throw new Exception();
                        } else {
                            ftp.changeWorkingDirectory(tempPath);
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Upload the file to the FTP server.
     * @param ftpFileName server file name (with absolute path)
     * @param input local file to upload
     */
    public void upload(String ftpFileName, InputStream input) throws Exception{
        try {
            if (!ftp.storeFile(ftpFileName, input)) {
                throw new Exception();
            }
        } finally {
            try {
                input.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Get file from ftp server into given output stream
     * @param ftpFileName file name on ftp server
     * @param out OutputStream
     * @throws IOException on I/O errors
     */
    public void download(String ftpFileName, OutputStream out) throws IOException {
        try {
            // Get file info.
            FTPFile[] fileInfoArray = ftp.listFiles(ftpFileName);
            if (fileInfoArray == null || fileInfoArray.length == 0) {
                throw new FileNotFoundException("File '" + ftpFileName + "' was not found on FTP server.");
            }
            // Check file size.
            FTPFile fileInfo = fileInfoArray[0];
            long size = fileInfo.getSize();
            if (size > Integer.MAX_VALUE) {
                throw new IOException("File '" + ftpFileName + "' is too large.");
            }
            // Download file.
            if (!ftp.retrieveFile(ftpFileName, out)) {
                throw new IOException("Error loading file '" + ftpFileName + "' from FTP server. Check FTP permissions and path.");
            }
            out.flush();
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * Delete the file from the FTP server.
     * @param ftpFileName server file name (with absolute path)
     * @throws IOException on I/O errors
     */
    public void deleteFile(String ftpFileName) throws IOException {
        if (!ftp.deleteFile(ftpFileName)) {
            throw new IOException("Can't remove file '" + ftpFileName + "' from FTP server.");
        }
    }

}

文件上传下载控制器

这里只提供一下大致思路,具体的代码就得自己根据业务来写了

package com.mine.risk.controller;

import com.mine.risk.bean.entity.SysFileEntity;
import com.mine.risk.bean.enumeration.ResponseEnum;
import com.mine.risk.bean.pojo.Message;
import com.mine.risk.bean.vo.SysFileVo;
import com.mine.risk.service.SysFileService;
import com.mine.risk.util.FtpUtil;
import com.mine.risk.util.ResponseUtil;
import com.mine.risk.util.SnowFlakeIdWorker;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;

/**
 * 文件上传控制器
 * @author spirit
 * @version 1.0
 * @date 2019-06-26 11:07
 */
@Controller
@RequestMapping("file")
public class FileController {

    /** 主机 */
    @Value("${ftp.address}")
    private String host;
    /** 端口 */
    @Value("${ftp.port}")
    private int port;
    /** ftp用户名 */
    @Value("${ftp.username}")
    private String userName;
    /** ftp用户密码 */
    @Value("${ftp.password}")
    private String passWord;
    /** 文件在服务器端保存的主目录 */
    @Value("${ftp.basePath}")
    private String basePath;
    /** 访问图片时的基础url */
    @Value("${file.base.url}")
    private String baseUrl;

    @Resource
    private SnowFlakeIdWorker snowFlakeIdWorker;
    @Resource
    private SysFileService sysFileService;

    @RequestMapping(value = "upload", method = RequestMethod.POST)
    @ResponseBody
    public Message fileUpload(@RequestParam(value="file") MultipartFile file){
        Message message;
        try {
            // 1、获取原始文件名
            String oldName = file.getOriginalFilename();
            // 2、获取文件后缀名
            String extraFileName = oldName.substring(oldName.lastIndexOf("."));
            // 3、生成新的文件名
            String newName = snowFlakeIdWorker.getNextId() + extraFileName;
            // 4、生成文件在服务器端存储的子目录
            String filePath = new SimpleDateFormat("yyyy/MM/dd/").format(new Date());
            StringBuilder tempPath = new StringBuilder();
            String[] dirs = filePath.split("/");
            for (String string : dirs) {
                tempPath.append(string);
            }
            // 6、获取上传的io流
            InputStream input = file.getInputStream();
            // 7、调用FtpUtil工具类进行上传,获取返回结果
            FtpUtil ftpUtil = new FtpUtil("UTF-8");
            try {
                ftpUtil.connect(host, port, userName, passWord);
                ftpUtil.makeDir(basePath, tempPath.toString());
                ftpUtil.upload(newName, input);
                // 上传成功后返回数据
                SysFileVo sysFileVo = new SysFileVo();
                sysFileVo.setOriginalName(oldName);
                sysFileVo.setNewName(newName);
                sysFileVo.setFileType(extraFileName.substring(1));
                sysFileVo.setFileSize(file.getSize());
                sysFileVo.setRootDirectory(tempPath.toString());
                sysFileService.saveFile(sysFileVo);
                return ResponseUtil.success(sysFileVo);
            } catch (Exception e) {
                e.printStackTrace();
                return ResponseUtil.info(ResponseEnum.BUSINESS_ERROR);
            } finally {
                // 用完了之后关闭连接
                ftpUtil.disconnect();
            }
        } catch (IOException e) {
            e.printStackTrace();
            message = ResponseUtil.info(ResponseEnum.SERVICE_ERROR);
        }
        return message;
    }

    @RequestMapping(value = "download")
    @ResponseBody
    public void downloadFile(String fileName, HttpServletResponse response){
        if(Objects.nonNull(fileName) && !StringUtils.isEmpty(fileName)){
            SysFileEntity fileEntity = sysFileService.findByNewName(fileName);
            if(Objects.nonNull(fileEntity)){
                FtpUtil ftpUtil = new FtpUtil("UTF-8");
                try {
                    // 清空response
                    response.reset();
                    // 设置response的Header
                    response.addHeader("Content-Disposition", "attachment;filename="+new String( fileEntity.getOriginalName().getBytes("gb2312"), "ISO8859-1" ) );
                    response.setContentType("application/octet-stream");
                    response.setContentLengthLong(fileEntity.getFileSize());
                    // 开始下载
                    ftpUtil.connect(host, port, userName, passWord);
                    BufferedOutputStream bus = new BufferedOutputStream(response.getOutputStream());
                    ftpUtil.download(basePath+"/"+fileEntity.getRootDirectory()+"/"+fileEntity.getNewName(), bus);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    // 用完了之后关闭连接
                    ftpUtil.disconnect();
                }
            }
        }
    }

}

vsftpd状态码解读

110    Restart marker reply.                              重新启动标记答复。
120    Service ready in nnn minutes.                      服务已就绪,在nnn分钟后开始。  
125    Data connection already open; transfer starting.   数据连接已打开,正在开始传输。
150    File status okay; about to open data connection.   文件状态正常,准备打开数据连接。
200    Command okay.                                      命令确定。
202    Command not implemented, superfluous at this site. 未执行命令,站点上的命令过多
211    System status, or system help reply.               系统状态,或系统帮助答复。
212    Directory status.                                  目录状态。
213    File status.                                       文件状态。
214    Help message.                                      帮助消息。
215    NAME system type.                    NAME系统类型,其中,NAME是Assigned Numbers文档中所列的正式系统名称。
220    Service ready for new user.                        服务就绪,可以执行新用户的请求。
221    Service closing control connection.                服务关闭控制连接。如果适当,请注销。
225    Data connection open; no transfer in progress.     数据连接打开,没有进行中的传输。
226    Closing data connection.                 关闭数据连接。请求的文件操作已成功(例如,传输文件或放弃文件)。
227    Entering Passive Mode <h1,h2,h3,h4,p1,p2>.         进入被动模式(h1,h2,h3,h4,p1,p2)。
228    Entering Long Passive Mode.
229    Extended Passive Mode Entered.
230    User logged in, proceed.                           用户已登录,继续进行。
250    Requested file action okay, completed.             请求的文件操作正确,已完成。
257    "PATHNAME" created.                                已创建“PATHNAME”。
331    User name okay, need password.                     用户名正确,需要密码。
332    Need account for login.                            需要登录帐户。
350    Requested file action pending further information. 请求的文件操作正在等待进一步的信息。
421    Service not available, closing control connection.
        服务不可用,正在关闭控制连接。如果服务确定它必须关闭,将向任何命令发送这一应答。
425    Can't open data connection.                        无法打开数据连接。
426    Connection closed; transfer aborted.               连接被关闭,数据传输中断。
450    Requested file action not taken.                   未执行请求的文件操作。文件不可用。
451    Requested action aborted. Local error in processing.请求的操作异常终止:正在处理本地错误。
452    Requested action not taken.                        未执行请求的操作。系统存储空间不够。
500    Syntax error, command unrecognized.      语法错误,命令无法识别。这可能包括诸如命令行太长之类的错误。
501    Syntax error in parameters or arguments.           在参数中有语法错误。
502    Command not implemented.                           未执行命令。
503    Bad sequence of commands.                          错误的命令序列。
504    Command not implemented for that parameter.        未执行该参数的命令。
521    Supported address families are <af1, .., afn>
522    Protocol not supported.
530    Not logged in.                                     未登录。
532    Need account for storing files.                    存储文件需要帐户。
550    Requested action not taken.                        未执行请求的操作。文件不可用。
551    Requested action aborted. Page type unknown.       请求的操作异常终止:未知的页面类型。
552    Requested file action aborted.                     请求的文件操作异常终止:超出存储分配(对于当前目录或数据集)。
553    Requested action not taken.                        未执行请求的操作。不允许的文件名。
554    Requested action not taken: invalid REST parameter.
555    Requested action not taken: type or struct mismatch.

最后,大家有什么不懂的或者其他需要交流的内容,也可以进入我的QQ讨论群一起讨论:654331206

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值