一蒲障、SFTP簡述
二锡宋、SFTP服務(wù)配置(基于CentOS 7)
三描焰、SFTP常用命令
四媳否、Java代碼實(shí)現(xiàn)SFTP操作(JSch實(shí)現(xiàn)上傳、下載荆秦、監(jiān)視器)
源碼請見Github:https://github.com/qiezhichao/CodeHelper/tree/master/j_sftp
五篱竭、踩坑記錄
一、SFTP簡述
sftp(Secure File Transfer Protocol)是一種安全的文件傳送協(xié)議步绸,是ssh內(nèi)含協(xié)議掺逼,也就是說只要sshd服務(wù)器啟動(dòng)了,sftp就可使用瓤介,不需要額外安裝坪圾,它的默認(rèn)端口和SSH一樣為22晓折。
sftp通過使用加密/解密技術(shù)來保障傳輸文件的安全性,因此sftp的傳輸效率比普通的FTP要低兽泄,但sftp的安全性要比ftp高漓概,因此sftp通常用于報(bào)表、對賬單等對安全性要求較高的場景病梢。
二胃珍、SFTP服務(wù)配置(基于Centos 7)
在CentOS 7系統(tǒng)中按照如下步驟配置sftp服務(wù)
1、使用root用戶查看openssh的版本:版本需大于4.8p1
ssh -V // 如果版本過低蜓陌,需要先升級
2觅彰、使用root用戶創(chuàng)建用戶組,組名為sftpgroup钮热;創(chuàng)建sftp用戶填抬,用戶名為sftpuser,并設(shè)置密碼
groupadd sftpgroup // 創(chuàng)建sftp組
useradd -g sftpgroup -M -s /sbin/nologin sftpuser //-M 表示創(chuàng)建用戶時(shí)不生成對應(yīng)home目錄隧期,-s /sbin/nologin 表示sftp用戶不能登錄系統(tǒng)
passwd sftpuser // 修改sftp用戶密碼
3飒责、修改配置文件sshd_config
vi /etc/ssh/sshd_config
修改如下:
將下面這行注釋掉
#Subsystem sftp /usr/libexec/openssh/sftp-server
## 在文件末尾添加如下幾行
Subsystem sftp internal-sftp
Match Group sftpgroup
X11Forwarding no
AllowTcpForwarding no
ChrootDirectory %h
ForceCommand internal-sftp
4、使用root用戶新建目錄/home/sftpfile仆潮,將其設(shè)置為sftpuser的home目錄宏蛉,并指定目錄權(quán)限
mkdir -p /sftp/sftpuser //-p 表示parents,即遞歸創(chuàng)建目錄
usermod -d /sftp/sftpuser sftpuser // -d 表示修改用戶home目錄// 設(shè)置Chroot目錄權(quán)限
chown root:sftpgroup /sftp/sftpuser
chmod 755 /sftp/sftpuser// 設(shè)置sftp用戶可以操作的目錄
mkdir /sftp/sftpuser/upload
chown sftpuser:sftpgroup /sftp/sftpuser/upload
chmod 755 /sftp/sftpuser/upload
5性置、重啟SSH
systemctl restart sshd.service
6拾并、驗(yàn)證:切換到sftpuser用戶進(jìn)行驗(yàn)證
sftp sftpuser@127.0.0.1
三、SFTP常用命令
sftp的常用命令和ftp基本相同鹏浅,使用help命令即可查詢
四嗅义、Java代碼實(shí)現(xiàn)SFTP操作
Java操作sftp需要使用一個(gè)開源包jsch,官網(wǎng)地址為 http://www.jcraft.com/jsch/隐砸,Maven項(xiàng)目中通過在pom.xml中引入如下依賴芥喇,如果需要其他版本,可在Maven中央倉庫http://mvnrepository.com/ 查詢凰萨。
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.54</version>
</dependency>
Jsch提供了sftp的各類操作的Java實(shí)現(xiàn),ChannelSftp類是實(shí)現(xiàn)SFTP操作的核心類械馆,sftp的命令即為該類中的方法胖眷,可以對比上圖的sftp常用命令,如:sftp命令中l(wèi)s為展示目錄下的文件列表霹崎,則ChannelSftp類中有 ls() 方法與其對應(yīng)珊搀。
1、sftp服務(wù)連接和關(guān)閉
private void connect(SFTPConfig sftpConfig) {
try {
// 通過JSch對象獲取session對象
session = new JSch().getSession(
sftpConfig.getSftpUserName(), // sftp用戶名
sftpConfig.getSftpHost(), // sftp主機(jī)IP
sftpConfig.getSftpPort()); // sftp端口
if (null != sftpConfig.getSftpPassword()) {
session.setPassword(sftpConfig.getSftpPassword()); // sftp用戶密碼
}
if (null != sftpConfig.getTimeout()) {
session.setTimeout(sftpConfig.getTimeout()); // 超時(shí)時(shí)間
}
session.setConfig("StrictHostKeyChecking", "no"); // 讓ssh客戶端自動(dòng)接受新主機(jī)的hostkey
session.connect();
this.channelSftp = (ChannelSftp) session.openChannel("sftp"); // 打開sftp渠道尾菇,除sftp外還有shell境析、X11等類型
this.channelSftp.connect();
} catch (JSchException e) {
this.close();
e.printStackTrace();
}
}
public void close() {
channelSftp.quit();
if (null != channelSftp) {
channelSftp.disconnect();
}
if (null != session) {
session.disconnect();
}
}
2囚枪、JSch的傳輸模式
JSch有三種文件傳輸模式:
(1)OVERWRITE:完全覆蓋模式。JSch的默認(rèn)文件傳輸模式劳淆,傳輸?shù)奈募⒏采w目標(biāo)文件链沼。
(2)APPEND:追加模式。如果目標(biāo)文件已存在沛鸵,則在目標(biāo)文件后追加括勺。
(3)RESUME:恢復(fù)模式。如果文件正在傳輸時(shí)曲掰,由于網(wǎng)絡(luò)等原因?qū)е聜鬏斨袛嗉埠矗瑒t下一次傳輸相同的文件
時(shí),會(huì)從上一次中斷的地方續(xù)傳栏妖。
3乱豆、sftp上傳
JSch為每種傳輸模式提供了3類不同的上傳方法
(1)最常用也是最簡單的調(diào)用
/**
* @param sftpParams
* @param channelSftpModel 調(diào)用的模式: ChannelSftp.OVERWRITE,ChannelSftp.APPEND吊趾,ChannelSftp.RESUME
* @throws SFTPException
*/
public void upload(SFTPParams sftpParams, int channelSftpModel) throws SFTPException {
try {
channelSftp.put(sftpParams.getLocalFilepath(), sftpParams.getRemoteFilepath(), channelSftpModel);
} catch (SftpException e) {
throw new SFTPException("Upload [" + sftpParams.getLocalFilepath() + "] to SFTP "
+ sftpConfig.getSftpHost() + ":" + sftpConfig.getSftpPort()
+ "[" + sftpParams.getRemoteFilepath() + "]" + " error.", e);
}
}
(2)基于InputStream的調(diào)用
這種方法適用于原始文件不存在宛裕,需要保存到遠(yuǎn)程目錄的數(shù)據(jù)來源于網(wǎng)絡(luò)或者代碼生成,當(dāng)然原始文件如果存在趾徽,也可以通過FileInputStream上傳续滋。
/**
*
* @param sftpParams
* @param channelSftpModel 調(diào)用的模式: ChannelSftp.OVERWRITE,ChannelSftp.APPEND孵奶,ChannelSftp.RESUME
* @param src 輸入流
* @throws SFTPException
*/
public void upload(SFTPParams sftpParams, int channelSftpModel, InputStream src) throws SFTPException {
try {
channelSftp.put(src, sftpParams.getRemoteFilepath(), channelSftpModel);
} catch (SftpException e) {
throw new SFTPException("Upload [" + sftpParams.getLocalFilepath() + "] to SFTP "
+ sftpConfig.getSftpHost() + ":" + sftpConfig.getSftpPort()
+ "[" + sftpParams.getRemoteFilepath() + "]" + " error.", e);
}
}
(3)基于OutputStream的調(diào)用
通過向put()方法返回的輸出流中寫入數(shù)據(jù)的方式來保存文件疲酌,這種方式可以自定義輸出流的數(shù)據(jù)塊大小(Jsch默認(rèn)數(shù)據(jù)塊大小為32KB)
/**
* @param sftpParams
* @param channelSftpModel 調(diào)用的模式: ChannelSftp.OVERWRITE了袁,ChannelSftp.APPEND朗恳,ChannelSftp.RESUME
* @param src 輸入流
* @param bufferSize 數(shù)據(jù)塊大小
* @throws SFTPException
*/
public void upload(SFTPParams sftpParams, int channelSftpModel, InputStream src, int bufferSize) throws SFTPException {
OutputStream out = null;
try {
out = channelSftp.put(sftpParams.getRemoteFilepath(), channelSftpModel);
byte[] buff = new byte[bufferSize]; // 設(shè)定每次傳輸?shù)臄?shù)據(jù)塊大小
int read;
if (out != null) {
do {
read = src.read(buff, 0, buff.length);
if (read > 0) {
out.write(buff, 0, read);
}
out.flush();
} while (read >= 0);
}
} catch (IOException e) {
throw new SFTPException("Upload [" + sftpParams.getLocalFilepath() + "] to SFTP "
+ sftpConfig.getSftpHost() + ":" + sftpConfig.getSftpPort()
+ "[" + sftpParams.getRemoteFilepath() + "]" + " error.", e);
} catch (SftpException e) {
throw new SFTPException("Upload [" + sftpParams.getLocalFilepath() + "] to SFTP "
+ sftpConfig.getSftpHost() + ":" + sftpConfig.getSftpPort()
+ "[" + sftpParams.getRemoteFilepath() + "]" + " error.", e);
} finally {
if (null != out) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4、sftp下載
JSch提供了3類不同的下載方法载绿,JSch提供的下載方法粥诫,如果沒有顯示指明傳輸模式,則默認(rèn)為覆蓋模式
(1)最常用也是最簡單的調(diào)用
public void download(SFTPParams sftpParams) throws SFTPException {
try {
channelSftp.get(sftpParams.getRemoteFilepath(), sftpParams.getLocalFilepath());
} catch (SftpException e) {
throw new SFTPException("Download [" + sftpParams.getRemoteFilepath() + "] from SFTP "
+ sftpConfig.getSftpHost() + ":" + sftpConfig.getSftpPort() + " error.", e);
}
}
(2)基于OutputStream的調(diào)用
打開一個(gè)輸出流崭庸,將遠(yuǎn)程文件寫入輸出流中怀浆。如,通過FileOutPutStream得到一個(gè)本地文件輸出流怕享,調(diào)用該方法將遠(yuǎn)程文件數(shù)據(jù)寫入該輸出流执赡,默認(rèn)數(shù)據(jù)塊大小為32KB。
public void download(SFTPParams sftpParams, OutputStream os) throws SFTPException {
try {
channelSftp.get(sftpParams.getRemoteFilepath(), os);
} catch (SftpException e) {
throw new SFTPException("Download [" + sftpParams.getRemoteFilepath() + "] from SFTP "
+ sftpConfig.getSftpHost() + ":" + sftpConfig.getSftpPort() + " error.", e);
}
}
(3)基于InputStream調(diào)用
將遠(yuǎn)程文件數(shù)據(jù)轉(zhuǎn)換成一個(gè)輸入流函筋,之后可以通過代碼從輸入流中拿取數(shù)據(jù)
public InputStream download2InputStream(SFTPParams sftpParams) throws SFTPException {
try {
return channelSftp.get(sftpParams.getRemoteFilepath());
} catch (SftpException e) {
throw new SFTPException("Download [" + sftpParams.getRemoteFilepath() + "] from SFTP "
+ sftpConfig.getSftpHost() + ":" + sftpConfig.getSftpPort() + " error.", e);
}
}
5沙合、監(jiān)聽器
JSch可以文件傳輸時(shí),對傳輸進(jìn)度進(jìn)行監(jiān)控跌帐,通過實(shí)現(xiàn)JSch提供的SftpProgressMonitor接口來實(shí)現(xiàn)監(jiān)聽器的功能首懈。
SftpProgressMonitor接口定義如下
package com.jcraft.jsch;
public interface SftpProgressMonitor{
public static final int PUT=0;
public static final int GET=1;
public static final long UNKNOWN_SIZE = -1L;
// 傳輸開始時(shí)绊率,調(diào)用init方法。其中op為操作類型究履,即為上面定義的PUT/GET滤否,max為文件的大小
void init(int op, String src, String dest, long max);
// 當(dāng)每次傳輸一個(gè)數(shù)據(jù)塊后,調(diào)用count方法挎袜,參數(shù)為這一次傳輸?shù)臄?shù)據(jù)塊大小
boolean count(long count);
// 傳輸結(jié)束時(shí)顽聂,調(diào)用end方法
void end();
}
現(xiàn)在實(shí)現(xiàn)一個(gè)每隔1秒,獲取上傳的進(jìn)度的功能
public class TimerSFTPProgressMonitor implements SftpProgressMonitor {
private boolean isTransEnd = false; // 是否傳輸完成
private long fileTotalSize; // 需要傳輸文件的大小
private long fileTransferedSize; // 已傳輸?shù)拇笮? private ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
public void init(int op, String src, String dest, long max) {
System.out.println("Begin transferring.");
fileTotalSize = max;
if (fileTotalSize != 0) {
final DecimalFormat df = new DecimalFormat("#.##");
service.scheduleAtFixedRate(new Runnable() {
public void run() {
if (!isTransEnd) {
if (fileTransferedSize != fileTotalSize) {
double d = ((double) fileTransferedSize * 100) / (double) fileTotalSize;
System.out.println("Current progress: " + df.format(d) + "%");
} else {
isTransEnd = true; // 已傳輸大小等于文件總大小盯仪,則已完成
}
}
}
}, 0, 1, TimeUnit.SECONDS);
}
}
public boolean count(final long count) {
fileTransferedSize = fileTransferedSize + count;
return true;
}
public void end() {
service.shutdown();
System.out.println("End transferring, transferedSize : " + fileTransferedSize);
}
}
最后效果如下
源碼請見Github:https://github.com/qiezhichao/CodeHelper/tree/master/j_sftp
五紊搪、踩坑記錄
1、JSch實(shí)現(xiàn)sftp上傳時(shí)全景,2: No such file
原因:在SFTP服務(wù)配置一節(jié)中耀石,我們將文件上傳到/sftp/sftpuser/upload,但是在代碼中不能直接寫入這個(gè)路徑爸黄,而需要寫入 /upload 這個(gè)路徑滞伟,因?yàn)閷τ趕ftpuser來說,它是沒有/sftp/sftpuser/這個(gè)路徑的炕贵“鹉危可以通過sftp的 ls / 命令來查看