FTP,即:文件传输协议(File Transfer Protocol),基于客户端/服务器模式,默认使用20、21端口号,其
中端口20(数据端口)用于进行数据传输,端口21(命令端口)用于接受客户端发出的相关FTP命令与参
数。FTP服务器普遍部署于局域网中,具有容易搭建、方便管理的特点。而且有些FTP客户端工具还可以支
持文件的多点下载以及断点续传技术,因此FTP服务得到了广大用户的青睐。
FTP协议有以下两种工作模式:
主动模式(PORT):FTP服务器主动向客户端发起连接请求。
被动模式(PASV):FTP服务器等待客户端发起连接请求(FTP的默认工作模式)。
vsftpd是一款运行在Linux操作系统上的FTP服务程序,具有很高的安全性和传输速度。
vsftpd有以下三种认证模式:
匿名开放模式:是一种最不安全的认证模式,任何人都可以无需密码验证而直接登陆。
本地用户模式:是通过Linux系统本地的账户密码信息进行认证的模式,相较于匿名开放模式更安全,而且
配置起来简单。
虚拟用户模式:是这三种模式中最安全的一种认证模式,它需要为FTP服务单独建立用户数据库文件,虚
拟出用来进行口令验证的账户信息,而这些账户信息在服务器系统中实际上是不存在的,仅供FTP服务程
序进行认证使用。
下面以虚拟用户模式配置讲解
1.输入图中命令,若输出图中信息表示已安装vsftpd,否则输入命令:yum install -y vsftpd 进行安装
2.安装完成后
vsftpd缺省安装在/etc/vsftpd/目录中,进入该目录后如下所示
1.vsftpd.conf:为核心配置文件
2.user_list:指定允许使用vsftpd 的用户列表文件,即白名单
3.ftpusers:指定哪些用户不能访问FTP服务器,即黑名单
4.如需去掉配置文件里的注释行输入以下命令即可
mv /etc/vsftpd/vsftpd.conf /etc/vsftpd/vsftpd.conf.bak
grep -v "#" /etc/vsftpd/vsftpd.conf.bak > /etc/vsftpd/vsftpd.conf
5.vsftpd.conf常用配置参数讲解
listen= # YES: 服务以独立运行方式运行; NO: 运行在 xinetd 内。 默认为 YES
listen_address= # 服务监听地址, 如果有多个网卡, 需要将服务绑定到指定 IP 地址
listen_port= # 服务监听端口, 默认为 21
anonymous_enable= # 是否允许匿名用户访问, 默认 NO
anon_mkdir_write_enable= # 是否允许匿名用户创建文件夹, 默认 NO
anon_other_write_enable= # 是否允许匿名用户其他的写权限, 创建文件、重命名、删除文件等权限 默认为 NO
anon_upload_enable= # 是否允许匿名用户上传, 默认 NO
anon_umask= # 匿名用户上传的文件的生成掩码, 默认为077
anon_max_rate= # 匿名用户的最大传输速率, 单位为 Byte/s, 值为 0 表示不限制
anon_world_readable_only= # 是否允许匿名用户只读浏览
local_enable= # 是否支持本地用户帐号访问
write_enable= # 是否开放本地用户的写权限
local_umask= # 本地用户上传的文件的生成掩码, 默认为077
local_max_rate= # 本地用户最大的传输速率, 单位为 Byte/s,值为0表示不限制
local_root= # 本地用户登陆后的目录,默认为本地用户的主目录
chroot_local_user= # 本地用户是否可以执行 chroot, 默认为 NO
chroot_list_enable= # 是否只有指定的用户才能执行 chroot, 默认为 NO
chroot_list_file= # 当 chroot_local_user=NO 且 chroot_list_enable=YES 时,
# 只有 filename 文件内指定的用户(每行一个用户名)可以执行 chroot,
# 默认值为 /etc/vsftpd.chroot_list
userlist_enable= # 是否启用 userlist_file 白/黑名单用户列表, 默认为 NO
userlist_deny= # 当 userlist_enable=YES(即启用 userlist_file )时, 则该字段才有效。
userlist_deny=YES: userlist_file 为 黑名单, 即在该文件内的用户均不可登录, 其他用户可以登录
userlist_deny=NO: userlist_file 为 白名单, 即在该文件内的用户才可以登录, 其他用户均不可登录
userlist_file= # 黑/白名单用户列表文件(每行一个用户名)
# 是黑名单还是白名单, 根据 userlist_deny 的值决定
# 默认值为 /etc/vsftpd.user_list
ftpd_banner= # 客户端连接服务器后显示的欢迎信息
connect_timeout= # 远程客户端响应端口数据连接超时时间, 单位为秒, 默认 60
accept_connection_timeout= # 空闲的数据连接超时时间, 单位为秒, 默认 120
data_connection_timeout= # 空闲的用户会话超时时间, 单位为秒, 默认 300
max_clients= # 在独立模式运行时, 最大连接数, 0 表示无限制
max_per_ip= # 在独立模式运行时, 每 IP 的最大连接数, 0表示无限制
pasv_min_port=45000 # PASV模式最小端口
pasv_max_port=49999 # PASV模式最大端口
3.配置防火墙开放vsftpd命令端口与PASV模式下的端口
firewall-cmd --zone=public --add-port=21/tcp --permanent
firewall-cmd --zone=public --add-port=45000-49000/tcp --permanent
firewall-cmd --reload
4.创建用于FTP认证的用户数据库文件
vim /etc/vsftpd/vuser.txt
其中奇数行为用户名,偶数行为密码
如:
xuyuanyuan
123456
chendanting
123456
明文信息不安全,需要使用db_load命令用哈希(hash)算法将明文信息转换成数据文件,然后将明文信息文件删除
db_load -T -t hash -f /etc/vsftpd/vuser.txt /etc/vsftpd/vuser.db
chmod 600 /etc/vsftpd/vuser.db
rm -f /etc/vsftpd/vuser.txt
5.创建虚拟用户映射的系统本地用户和FTP根目录
输入命令:
useradd -d /ftp_data -s /sbin/nologin virtual
chmod -Rf 755 /ftp_data
6.建立用于支持虚拟用户的PAM文件
PAM(可插拔认证模块)是一种认证机制,通过一些动态链接库和统一的API把系统提供的服务与认证方式分
开,使得系统管理员可以根据需求灵活调整服务程序的不同认证方式。PAM采用了分层设计(应用程序层、
应用接口层、鉴别模块层)的思想,其结构如下图所示。
vim /etc/pam.d/vsftpd.vu
添加以下信息
auth required pam_userdb.so db=/etc/vsftpd/vuser
account required pam_userdb.so db=/etc/vsftpd/vuser
6.为vuser.txt里的用户配置相关参数
(1) 创建存放用户的目录
mkdir /etc/vsftpd/vusers_dir
(2) 为各用户创建文件,文件名即为用户名
vim /etc/vsftpd/vusers_dir/xuyuanyuan
vim /etc/vsftpd/vusers_dir/chendanting
(3) 按各用户需要加入参数配置
local_root=/ftp_data/xuyuanyuan
anon_upload_enable=YES
anon_mkdir_write_enable=YES
anon_other_write_enable=YES
anon_world_readable_only=YES
6.修改核心配置文件vsftpd.conf为以下内容
anonymous_enable=NO
local_enable=YES
# 开启虚拟用户模式
guest_enable=YES
# 指定虚拟用户对应的系统用户
guest_username=virtual
# 允许对ftp根目录执行写入操作
allow_writeable_chroot=YES
write_enable=YES
local_umask=022
dirmessage_enable=YES
xferlog_enable=YES
connect_from_port_20=YES
xferlog_std_format=YES
listen_port=21
listen=NO
listen_ipv6=YES
#PAM文件
pam_service_name=vsftpd.vu
userlist_enable=YES
tcp_wrappers=YES
# 虚拟用户配置文件目录
user_config_dir=/etc/vsftpd/vuser_dir
# 被动模式端口范围
pasv_min_port=45000
pasv_max_port=49000
chroot_local_user=YES
7.最后启动服务即可访问
systemctl start vsftpd.serivce 启动服务
systemctl stop vsftpd.serivce 停止服务
systemctl status vsftpd.serivce 查看服务状态
systemctl restart vsftpd.serivce 查看服务状态
在浏览器输入 ftp://ip:21 就可访问了
最后配上操作ftp的工具类
maven依赖
commons-net
commons-net
3.6
1 /**
2 *@authorMedusa3 * @Date 12-14-194 * @Description ftp工具类5 */
6 public classFtpUtil {7
8 /**
9 * ftp服务器的ip10 */
11 privateString ip;12
13 /**
14 * ftp服务器的端口,缺省为2115 */
16 private intport;17
18 /**
19 * 连接ftp服务器的用户名20 */
21 privateString userName;22
23 /**
24 * 用户名对应通行证25 */
26 privateString password;27
28 /**
29 * 客户端编码30 */
31 private String clientCharset = "GBK";32
33 /**
34 * 服务端编码,缺省为ISO8859-135 */
36 private String serverCharset = "ISO8859-1";37
38 privateFTPClient ftpClient;39
40 public FtpUtil(String ip, intport, String userName, String password) {41 this.ip =ip;42 this.port =port;43 this.userName =userName;44 this.password =password;45 }46
47 /**
48 * 建立连接并登陆服务器49 */
50 public booleanconnectFtp() {51 ftpClient = newFTPClient();52
53 try{54 ftpClient.connect(ip, port);55
56 //是否连接成功
57 if (!FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {58 System.out.println("create connect fail");59 ftpClient.disconnect();60 return false;61 }62
63 if (!ftpClient.login(userName, password)) {64 System.out.println("login server fail");65 ftpClient.disconnect();66 return false;67 }68
69 //开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码,否则使用本地编码
70 if (FTPReply.isPositiveCompletion(ftpClient.sendCommand("OPTS UTF8", "ON"))) {71 clientCharset = "UTF-8";72 }73 ftpClient.setControlEncoding(clientCharset);74 ftpClient.enterLocalPassiveMode(); //设置被动模式
75 ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE); //设置文件传输类型
76 } catch(IOException e) {77 System.out.println("create connect or close connect error");78 return false;79 }80 return true;81 }82
83 /**
84 * 退出登陆并关闭连接85 */
86 public booleancloseFtp() {87 if (ftpClient != null &&ftpClient.isConnected()) {88 try{89 ftpClient.logout();90 ftpClient.disconnect();91 return true;92 } catch(IOException e) {93 System.out.println("logout or close connect error");94 return false;95 }96 }97 return false;98 }99
100 /**
101 * 客户端转服务端编码102 */
103 privateString clientCharsetToServer(String path) {104 try{105 return newString(path.getBytes(clientCharset), serverCharset);106 } catch(UnsupportedEncodingException e) {107 System.out.println("client to server encode error");108 return null;109 }110 }111
112 /**
113 * 服务端转客户端编码114 */
115 privateString serverCharsetToClient(String path) {116 try{117 return newString(path.getBytes(serverCharset), clientCharset);118 } catch(UnsupportedEncodingException e) {119 System.out.println("server to client encode error");120 return null;121 }122 }123
124 /**
125 * 获取当前工作目录126 */
127 privateString getWorkDirectory() {128 try{129 returnserverCharsetToClient(ftpClient.printWorkingDirectory());130 } catch(IOException e) {131 System.out.println("get work directory error");132 return null;133 }134 }135
136 /**
137 * 获取当前工作目录下的所有文件名138 */
139 privateString[] getWorkDirectoryFileNames() {140 try{141 FTPFile[] ftpFiles =ftpClient.listFiles();142 if (ftpFiles.length > 0) {143 String[] fileNames = newString[ftpFiles.length];144 int i = 0;145
146 for (FTPFile ftpFile: ftpFiles) fileNames[i++] =ftpFile.getName();147
148 returnfileNames;149 }150 return null;151 } catch(IOException e) {152 System.out.println("get work directory files error");153 return null;154 }155 }156
157 /**
158 * 根据从根目录开始的完整文件路径,切换工作目录159 */
160 private booleanchangeWorkDirectory(String path) {161 try{162 returnftpClient.changeWorkingDirectory(clientCharsetToServer(path));163 } catch(IOException e) {164 System.out.println("change directory error");165 return false;166 }167 }168
169 /**
170 * 当前工作目录下是否有查询的文件171 */
172 private booleanisWorkDirectoryHasFile(String fileName) {173 try{174 FTPFile[] ftpFiles =ftpClient.listFiles();175
176 for(FTPFile ftpFile : ftpFiles) {177 if (ftpFile.getName().equals(fileName)) return true;178 }179
180 return false;181 } catch(IOException e) {182 System.out.println("get work directory files error");183 return false;184 }185 }186
187 /**
188 *获取对应路径下的文件(根目录除外),并切换工作目录至此文件的父目录189 */
190 privateFTPFile getFTPFile(String path) {191 if (path.equals("/")) {192 System.out.println("invalid root directory");193 return null;194 }195
196 String fileName =path;197 if (!path.equals("/") && path.indexOf("/") != -1) {198 String[] split = path.split("/");199 fileName = split[split.length - 1];200 }201 if (changeWorkDirectory(path.substring(0, path.length() -fileName.length()))) {202 try{203 for(FTPFile ftpFile : ftpClient.listFiles()) {204 if (ftpFile.getName().equals(fileName)) returnftpFile;205 }206 } catch(IOException e) {207 System.out.println("get work directory files error");208 return null;209 }210 }211 return null;212 }213
214 /**
215 * 获取服务端文件夹下所有文件路径216 */
217 public List> getDirectoryUnderAllFile(String path) throwsIOException {218 //存放目录和文件的路径
219 List> directoryAndFilePaths = new ArrayList<>();220 //存放目录的路径
221 List directoryPaths = new ArrayList<>();222 //存放文件的路径
223 List filePaths = new ArrayList<>();224 //待遍历处理的文件夹路径
225 Queue ergodicDirPaths = new LinkedList<>();226 ergodicDirPaths.offer(path);227 directoryPaths.add(path);228
229 while (ergodicDirPaths.size() > 0) {230 String entry =ergodicDirPaths.poll();231 if(changeWorkDirectory(entry)) {232 entry = "/".endsWith(entry) ? "": entry;233 FTPFile[] ftpFiles =ftpClient.listFiles();234 for(FTPFile ftpFile : ftpFiles) {235 if(ftpFile.isFile()) {236 filePaths.add(entry + "/" +ftpFile.getName());237 } else{238 directoryPaths.add(entry + "/" +ftpFile.getName());239 ergodicDirPaths.offer(entry + "/" +ftpFile.getName());240 }241 }242 } else{243 System.out.println("切换目录失败");244 return null;245 }246 }247
248 directoryAndFilePaths.add(directoryPaths);249 directoryAndFilePaths.add(filePaths);250 returndirectoryAndFilePaths;251 }252
253 /**
254 * 获取客户端文件夹下所有文件路径255 */
256 public List>getDirectoryUnderAllFile1(String path) {257 //存放目录和文件的路径
258 List> directoryAndFilePaths = new ArrayList<>();259 //存放目录的路径
260 List directoryPaths = new ArrayList<>();261 //存放文件的路径
262 List filePaths = new ArrayList<>();263 //待遍历处理的文件夹路径
264 Queue ergodicDirPaths = new LinkedList<>();265 ergodicDirPaths.offer(path);266 directoryPaths.add(path);267
268 while (ergodicDirPaths.size() > 0) {269 String entry =ergodicDirPaths.poll();270
271 File dir = newFile(entry);272 for(File file : dir.listFiles()) {273 if(file.isFile()) {274 filePaths.add(entry + "/" +file.getName());275 } else{276 directoryPaths.add(entry + "/" +file.getName());277 ergodicDirPaths.offer(entry + "/" +file.getName());278 }279 }280 }281
282 directoryAndFilePaths.add(directoryPaths);283 directoryAndFilePaths.add(filePaths);284 returndirectoryAndFilePaths;285 }286
287 /**
288 * 下载文件,若客户端已存在则覆盖它289 */
290 public booleandownload(String serverPath, String clientPath) {291 //判断服务端文件是否存在
292 FTPFile ftpFile =getFTPFile(serverPath);293 if (ftpFile == null) {294 System.out.println("server file not exist");295 return false;296 }297
298 //若客户端目录不存在,就创建它,否则清空目录
299 File clientDirectory = newFile(clientPath);300 if (!clientDirectory.exists()) {301 if (!clientDirectory.mkdirs()) {302 System.out.println("client create directory fail");303 return false;304 }305 } else{306 if (!clientDirectory.isDirectory()) {307 System.out.println("clientPath is not directory");308 return false;309 } else{310 if (!emptyDirectory(clientDirectory)) {311 System.out.println("empty client directory fail");312 return false;313 }314 }315 }316
317 if(ftpFile.isFile()) {318 executeDownload(serverPath, clientPath + serverPath.substring(serverPath.lastIndexOf("/")));319 } else{320 try{321 List> allFile =getDirectoryUnderAllFile(serverPath);322 //获取所有目录路径
323 List directoryPaths = allFile.get(0);324 if (directoryPaths.size() > 0) {325 directoryPaths.forEach(path -> new File(clientPath +path).mkdir());326 } else{327 System.out.println("get all directory fail");328 return false;329 }330
331 //获取所有文件路径
332 List filePaths = allFile.get(1);333 for(String path : filePaths) {334 executeDownload(path, clientPath +path);335 }336 } catch(IOException e) {337 System.out.println("error getting all files under this path");338 return false;339 }340 }341 return true;342 }343
344 /**
345 * 执行下载文件346 */
347 private voidexecuteDownload(String serverPath, String clientPath) {348 InputStream is = null;349 OutputStream os = null;350 try{351 is =ftpClient.retrieveFileStream(clientCharsetToServer(serverPath));352 File file = newFile(clientPath);353 if (!file.createNewFile()) {354 System.out.println("client create file fail");355 return;356 }357 os = newFileOutputStream(file);358 byte[] by = new byte[1024];359 int len = 0;360 while ((len = is.read(by)) != -1) os.write(by, 0, len);361 } catch(IOException e) {362 System.out.println("stream handle file fail");363 return;364 } finally{365 try{366 if (is != null) {367 is.close();368 //必须调用此方法,否则第二次调用retrieveFileStream()方法时,返回null
369 ftpClient.completePendingCommand();370 }371 if (os != null) os.close();372 } catch(IOException e) {373 e.printStackTrace();374 }375 }376 }377
378 /**
379 * 执行上传文件380 */
381 private voidexecuteUpload(String serverPath, String clientPath) {382 InputStream is = null;383 OutputStream os = null;384 try{385 is = newFileInputStream(clientPath);386 os =ftpClient.storeFileStream(clientCharsetToServer(serverPath));387
388 byte[] by = new byte[1024];389 int len = 0;390 while ((len = is.read(by)) != -1) os.write(by, 0, len);391 } catch(FileNotFoundException e) {392 System.out.println("client file not found");393 return;394 } catch(IOException e) {395 System.out.println("stream handle file fail");396 return;397 } finally{398 try{399 if (is != null) is.close();400 if (os != null) {401 os.close();402 ftpClient.completePendingCommand();403 }404 } catch(IOException e) {405 e.printStackTrace();406 }407 }408 }409
410 /**
411 * 上传文件,若服务端已存在则覆盖它412 */
413 public booleanupload(String serverPath, String clientPath) {414 File clientFile = newFile(clientPath);415 if (!clientFile.exists()) {416 System.out.println("client file not exist");417 return false;418 }419
420 if (!"/".equals(serverPath)) {421 FTPFile ftpFile =getFTPFile(serverPath);422 if (ftpFile == null) {423 String[] split = serverPath.split("/");424 changeWorkDirectory("/");425 try{426 for (int i = 1; i < split.length; i++) {427 ftpClient.makeDirectory(clientCharsetToServer(split[i]));428 changeWorkDirectory(getWorkDirectory() + "/" +split[i]);429 }430 } catch(IOException e) {431 System.out.println("create server directory error");432 return false;433 }434 }435 }436
437 serverPath = "/".endsWith(serverPath) ? "": serverPath;438 if(clientFile.isFile()) {439 try{440 String filePath = serverPath + clientPath.substring(clientPath.lastIndexOf("/"));441 ftpClient.deleteFile(filePath);442 executeUpload(filePath, clientPath);443 } catch(IOException e) {444 System.out.println("delete server file error");445 return false;446 }447 } else{448 List> allFile =getDirectoryUnderAllFile1(clientPath);449 List directoryPaths = allFile.get(0);450 String path2;451 if (directoryPaths.size() > 0) {452 try{453 String firstDir = directoryPaths.get(0);454 String path1 = firstDir.substring(firstDir.lastIndexOf("/"));455 path2 = firstDir.substring(0, firstDir.lastIndexOf("/"));456 ftpClient.makeDirectory(clientCharsetToServer(serverPath +path1));457 for (int i = 1; i < directoryPaths.size(); i++) {458 String tempPath =directoryPaths.get(i).substring(path2.length());459 ftpClient.makeDirectory(clientCharsetToServer(serverPath +tempPath));460 }461 } catch(IOException e) {462 System.out.println("create server directory error");463 return false;464 }465 } else{466 System.out.println("get all directory fail");467 return false;468 }469 List filePaths = allFile.get(1);470 for(String path : filePaths) {471 String tempPath =path.substring(path2.length());472 executeUpload(serverPath +tempPath, path);473 }474 }475 return true;476 }477
478 /**
479 * 清空目录480 */
481 private booleanemptyDirectory(File dir) {482 File[] oneLevelFile =dir.listFiles();483 if (oneLevelFile.length > 0) {484 Stack willDeleteFile = new Stack<>();485 for (int i = 0; i < oneLevelFile.length; i++) {486 willDeleteFile.push(oneLevelFile[i]);487 }488
489 while (willDeleteFile.size() > 0) {490 File entry =willDeleteFile.pop();491 if(entry.isFile()) {492 if (!entry.delete()) return false;493 } else{494 File[] lowerLevelFile =entry.listFiles();495 if (lowerLevelFile.length > 0) {496 willDeleteFile.push(entry);497 for (int i = 0; i < lowerLevelFile.length; i++) {498 willDeleteFile.push(lowerLevelFile[i]);499 }500 } else{501 if (!entry.delete()) return false;502 }503 }504 }505 }506 return true;507 }508
509 /**
510 * web端上传511 */
512 public booleanwebUpload(MultipartFile file, String serverPath) {513 if (!"/".equals(serverPath)) {514 FTPFile ftpFile =getFTPFile(serverPath);515 if (ftpFile == null) {516 String[] split = serverPath.split("/");517 changeWorkDirectory("/");518 try{519 for (int i = 1; i < split.length; i++) {520 ftpClient.makeDirectory(clientCharsetToServer(split[i]));521 changeWorkDirectory(getWorkDirectory() + "/" +split[i]);522 }523 } catch(IOException e) {524 System.out.println("create server directory error");525 return false;526 }527 }528 }529 String filePath;530 try{531 serverPath = "/".endsWith(serverPath) ? "": serverPath;532 filePath = serverPath + "/" +file.getOriginalFilename();533 ftpClient.deleteFile(filePath);534 } catch(IOException e) {535 System.out.println("delete server file error");536 return false;537 }538
539 InputStream is = null;540 OutputStream os = null;541 try{542 is =file.getInputStream();543 os =ftpClient.storeFileStream(clientCharsetToServer(filePath));544
545 byte[] by = new byte[1024];546 int len = 0;547 while ((len = is.read(by)) != -1) os.write(by, 0, len);548 } catch(FileNotFoundException e) {549 System.out.println("client file not found");550 return false;551 } catch(IOException e) {552 System.out.println("stream handle file fail");553 return false;554 } finally{555 try{556 if (is != null) is.close();557 if (os != null) {558 os.close();559 ftpClient.completePendingCommand();560 }561 } catch(IOException e) {562 e.printStackTrace();563 }564 }565 return true;566 }567
568 /**
569 * web端下载570 */
571 public booleanwebDownload(HttpServletResponse response, String serverPath) {572 FTPFile ftpFile =getFTPFile(serverPath);573 if (ftpFile == null) {574 System.out.println("server file not exist");575 return false;576 }577
578 InputStream is = null;579 try{580 is =ftpClient.retrieveFileStream(clientCharsetToServer(serverPath));581 ServletOutputStream sos =response.getOutputStream();582 byte[] by = new byte[1024];583 int len = 0;584 while ((len = is.read(by)) != -1) sos.write(by, 0, len);585 } catch(IOException e) {586 System.out.println("stream handle file fail");587 return false;588 } finally{589 try{590 if (is != null) {591 is.close();592 ftpClient.completePendingCommand();593 }594 } catch(IOException e) {595 e.printStackTrace();596 }597 }598
599 return true;600 }601
602 }
View Code