SFTP服務(wù)配置以及命令/代碼操作

一蒲障、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 / 命令來查看

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市称开,隨后出現(xiàn)的幾起案子亩钟,更是在濱河造成了極大的恐慌,老刑警劉巖鳖轰,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件清酥,死亡現(xiàn)場離奇詭異,居然都是意外死亡蕴侣,警方通過查閱死者的電腦和手機(jī)焰轻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來昆雀,“玉大人辱志,你說我怎么就攤上這事∧欤” “怎么了揩懒?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長客冈。 經(jīng)常有香客問我,道長稳强,這世上最難降的妖魔是什么场仲? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任和悦,我火速辦了婚禮,結(jié)果婚禮上渠缕,老公的妹妹穿的比我還像新娘鸽素。我一直安慰自己,他們只是感情好亦鳞,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布馍忽。 她就那樣靜靜地躺著,像睡著了一般燕差。 火紅的嫁衣襯著肌膚如雪遭笋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天徒探,我揣著相機(jī)與錄音瓦呼,去河邊找鬼。 笑死测暗,一個(gè)胖子當(dāng)著我的面吹牛央串,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播碗啄,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼质和,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了稚字?” 一聲冷哼從身側(cè)響起饲宿,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎尉共,沒想到半個(gè)月后褒傅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡袄友,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年殿托,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片剧蚣。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡支竹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鸠按,到底是詐尸還是另有隱情礼搁,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布目尖,位于F島的核電站馒吴,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜饮戳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一豪治、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧扯罐,春花似錦负拟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至秸歧,卻和暖如春厨姚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背寥茫。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工遣蚀, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人纱耻。 一個(gè)月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓芭梯,卻偏偏與公主長得像,于是被迫代替她去往敵國和親弄喘。 傳聞我的和親對象是個(gè)殘疾皇子玖喘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容