SFTP和SSH的java实现

 sftp与ftp

要谈sftpSSH File Transfer Protocol),首先要谈ftpFile Transfer Protocol),大家都知道ftp是文件传输协议,它基于tcp协议,可以用来发送文件。

sftp与ssh

sftp,就是安全(security)的ftp,因为它是基于ssh协议,ssh 为 Secure Shell 的缩写,由 IETF 的网络小组(Network Working Group)所制定;SSH 为建立在应用层基础上的安全协议。SSH 是较可靠,专为远程登录会话和其他网络服务提供安全性的协议。利用 SSH 协议可以有效防止远程管理过程中的信息泄露问题ssh在连接和传送的过程中会加密所有的数据。所以通俗的来讲,通过ssh协议进行文件传输,那就是sftp。

那么如何使用ssh来实现文件传输呢?熟悉linux的伙伴们应该对ssh也不陌生,因为linux自带了ssh(嘘!其实我并不熟悉linux,只是知道linux自带了ssh,以前装了没多久就卸了,现在发现会linux逼格会提高不少。一定要再装一个玩一下!)。

遗憾的是,ssh基本上是基于linux和一些客户端安装软件。那么在我们平常的web开发中,要用sftp来传输文件怎么办呢?jsch就是解决办法了。

jsch简介

jsch是ssh的纯java实现。这么讲有点抽象,通俗说,你在官网上down下来就是一个jar包,引入你的项目,就可以开始使用

  • 第一步:首先在maven*仓库中查一下怎么在pom中依赖,可以点这里
  •  <!-- https://mvnrepository.com/artifact/com.jcraft/jsch -->
     <dependency>
        <groupId>com.jcraft</groupId>
        <artifactId>jsch</artifactId>
        <version>0.1.55</version>
    </dependency>
    

     

  • 第二步:创建一个工具类:SFTPClient.java, 实现文件上传、下载、读取等功能:代买如下:
  • package com.zjh.logviewer.ssh;
    
    import com.zjh.logviewer.model.FileAttri;
    import com.zjh.logviewer.model.Server;
    
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.nio.charset.Charset;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    import java.util.Vector;
    
    import org.apache.commons.lang3.ArrayUtils;
    
    import com.jcraft.jsch.ChannelSftp;
    import com.jcraft.jsch.ChannelSftp.LsEntry;
    import com.jcraft.jsch.JSchException;
    import com.jcraft.jsch.SftpATTRS;
    import com.jcraft.jsch.SftpException;
    
    public class SFTPClient extends BaseJchClient {
    
        private ChannelSftp channel;
    
        public SFTPClient(Server serverInfo) throws RemoteAccessException {
            super(serverInfo);
        }
    
        private ChannelSftp openChannel() throws RemoteAccessException {
            try {
                if (channel != null) {
                    if (channel.isConnected() || !channel.isClosed()) {
                        channel.disconnect();
                        channel = null;
                    } else {
                        return channel;
                    }
                }
                conn();
    
                channel = (ChannelSftp) session.openChannel("sftp");
                channel.connect(DEFAULT_CONN_TIMEOUT);
                return channel;
    
            } catch (JSchException e) {
                throw new RemoteAccessException(e);
            }
        }
    
        /**
         * 从sftp服务器下载指定文件到本地指定目录
         * 
         * @param remoteFile 文件的绝对路径+fileName
         * @param localPath 本地临时文件路径
         * @return
         */
        public boolean download(String remoteFile, String localPath) throws RemoteAccessException {
            ChannelSftp sftp = null;
            try {
                sftp = openChannel();
                sftp.get(remoteFile, localPath);
                return true;
            } catch (SftpException e) {
                logger.error("download remoteFile:{},localPath:{}, ex:{}", remoteFile, localPath, e);
                throw new RemoteAccessException(e);
            }
        }
    
        /**
         * 上传文件
         *
         * @param directory  上传的目录-相对于SFPT设置的用户访问目录, 为空则在SFTP设置的根目录进行创建文件(除设置了服务器全磁盘访问)
         * @param uploadFile 要上传的文件全路径
         */
        public  boolean upload(String directory, String uploadFile) throws Exception {
            ChannelSftp sftp = null;
            try {
                try {
                    sftp = openChannel();
                    sftp.cd(directory); // 进入目录
                } catch (SftpException sException) {
                    if (sftp.SSH_FX_NO_SUCH_FILE == sException.id) { // 指定上传路径不存在
                        sftp.mkdir(directory);// 创建目录
                        sftp.cd(directory); // 进入目录
                    }
                }
    
                File file = new File(uploadFile);
                InputStream in = new FileInputStream(file);
    
                sftp.put(in, file.getName());
                in.close();
            } catch (Exception e) {
                throw new Exception(e.getMessage(), e);
            }
            return true;
        }
    
        /**
         * 读取sftp上指定文件数据
         * 
         * @param remoteFile
         * @return
         */
        public byte[] getFile(String remoteFile) throws RemoteAccessException {
            ChannelSftp sftp = null;
            InputStream inputStream = null;
            try {
                sftp = openChannel();
                inputStream = sftp.get(remoteFile);
                return IOHelper.readBytes(inputStream);
            } catch (SftpException | IOException e) {
                logger.error("getFile remoteFile:{},ex:{}", remoteFile, e);
                throw new RemoteAccessException(e);
            } finally {
                IOHelper.closeQuietly(inputStream);
            }
        }
    
        /**
         * 读取sftp上指定(文本)文件数据,并按行返回数据集合
         *
         * @param remoteFile
         * @param charset
         * @return
         */
        public String getFileContent(String remoteFile, Charset charset) throws RemoteAccessException {
            ChannelSftp sftp = null;
            InputStream inputStream = null;
            try {
                sftp = openChannel();
                inputStream = sftp.get(remoteFile);
                return IOHelper.readText(inputStream, charset);
            } catch (SftpException | IOException e) {
                logger.error("getFileText remoteFile:{},error:{}", remoteFile, e);
                throw new RemoteAccessException(e);
            } finally {
                IOHelper.closeQuietly(inputStream);
            }
        }
    
        /**
         * 列出指定目录下文件列表
         * 
         * @param remotePath
         * @param descendant 是否递归查询子孙目录
         * @param excludes 要排除的文件
         * @return
         */
        public List<FileAttri> ls(String remotePath, boolean descendant, String... excludes) throws RemoteAccessException {
            ChannelSftp sftp = null;
            List<FileAttri> lsFiles;
            try {
                sftp = openChannel();
                lsFiles = ls(sftp, remotePath, descendant, excludes);
            } catch (SftpException e) {
                logger.error("ls remotePath:{} , error:{}", remotePath, e.getMessage());
                if ("Permission denied".equals(e.getMessage())) {
                    throw new PermissionException("没有文件读取权限");
                }
                throw new RemoteAccessException(e);
            }
            if (lsFiles != null) {
                Collections.sort(lsFiles);
            }
            return lsFiles;
        }
    
        @SuppressWarnings("unchecked")
        private List<FileAttri> ls(ChannelSftp sftp, String remotePath, boolean descendant, String... excludes)
                throws SftpException {
            List<FileAttri> lsFiles = new ArrayList<>();
            FileAttri tmpFileAttri;
            long tmpSize;
            String tmpFullPath;
            Vector<LsEntry> vector = sftp.ls(remotePath);
            for (LsEntry entry : vector) {
                if (".".equals(entry.getFilename()) || "..".equals(entry.getFilename())) {
                    continue;
                }
                tmpFullPath = UrlHelper.mergeUrl(remotePath, entry.getFilename());
                if (excludes != null && ArrayUtils.contains(excludes, tmpFullPath)) {
                    logger.debug("忽略目录:{}", tmpFullPath);
                    continue;
                }
                tmpFileAttri = new FileAttri();
                tmpFileAttri.setNodeName(entry.getFilename());
                tmpFileAttri.setLastUpdateDate(entry.getAttrs()
                        .getATime());
                tmpFileAttri.setPath(tmpFullPath);
    
                if (entry.getAttrs()
                        .isDir()) {
                    tmpFileAttri.setDir(true);
                    if (descendant) {
                        try {
                            List<FileAttri> childs = ls(sftp, tmpFileAttri.getPath(), descendant, excludes);
                            if (CollectionHelper.isNotEmpty(childs)) {
                                tmpFileAttri.addNodes(childs);
                            }
                        } catch (PermissionException e) {
                            tmpFileAttri.setNodeName(tmpFileAttri.getNodeName() + "[无权限]");
                        }
                    }
                } else {
                    tmpFileAttri.setDir(false);
                    tmpSize = entry.getAttrs()
                            .getSize();
                    if (tmpSize < 1024) {
                        tmpFileAttri.setSize(entry.getAttrs()
                                .getSize() + "B");
                    } else if (tmpSize >= 1024 && tmpSize < 1048576) {
                        tmpFileAttri.setSize(MathHelper.round((entry.getAttrs()
                                .getSize() / 1024f), 1) + "KB");
                    } else if (tmpSize > 1048576) {
                        tmpFileAttri.setSize(MathHelper.round((entry.getAttrs()
                                .getSize() / 1048576f), 2) + "MB");
                    }
                }
                lsFiles.add(tmpFileAttri);
            }
            return lsFiles;
        }
    
        /**
         * 判断文件是否存在
         * 
         * @param filePath
         * @return
         * @throws RemoteAccessException
         */
        public boolean isExist(String filePath) throws RemoteAccessException {
            ChannelSftp sftp = null;
            try {
                sftp = openChannel();
                SftpATTRS attrs = sftp.lstat(filePath);
                return attrs != null;
            } catch (SftpException e) {
                logger.error("文件不存在,remotePath:{} , error:{}", filePath, e.getMessage());
                return false;
            }
        }
    
        @Override
        public void close() throws IOException {
            if (channel != null) {
                try {
                    channel.disconnect();
                } catch (Exception e) {
                    channel = null;
                }
            }
            super.close();
        }
    }
    点击并拖拽以移动
    

      

    第三步:创建ssh交互的工具类:SSHClient.java, 实现执行单条命令、异步执行命令的功能:代码如下:huo获取

  • import com.zjh.logviewer.model.Server;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStream;
    import java.nio.charset.StandardCharsets;
    
    import com.jcraft.jsch.ChannelExec;
    import com.jcraft.jsch.JSchException;
    
    public class SSHClient extends BaseJchClient {
    
        public SSHClient(Server serverInfo) throws RemoteAccessException {
            super(serverInfo);
        }
    
        /**
         * 执行单条命令
         * 
         * @param cmd
         * @return StringBuffer命令结果
         * @throws RemoteAccessException
         */
        public StringBuffer exec(String cmd) throws RemoteAccessException {
            String line = null;
            BufferedReader reader = null;
            ChannelExec channelExec = null;
            StringBuffer resultBuf = new StringBuffer();
            InputStream inStream = null;
            try {
                channelExec = getChannel(cmd);
                stopLastCmdThread();// 中断前一个异步命令的执行
                inStream = channelExec.getInputStream();
                channelExec.connect();
                if (inStream != null) {
                    reader = IOHelper.toBufferedReader(inStream, StandardCharsets.UTF_8.name());
                    while ((line = reader.readLine()) != null) {
                        resultBuf.append(line)
                                .append(Chars.LF);
                    }
                }
            } catch (IOException | JSchException e) {
                logger.error("执行命令异常,ip:{},cmd:{},ex:{}", serverInfo.getIp(), cmd, e);
                throw new RemoteAccessException(e);
            } finally {
                IOHelper.closeQuietly(inStream);
                if (channelExec != null) {
                    channelExec.disconnect();
                }
            }
            if (logger.isDebugEnabled()) {
                logger.debug("{}执行命令:{},结果:{}", serverInfo.getIp(), cmd, resultBuf);
            }
            return resultBuf;
        }
    
        ExecAsyncCmdThread cmdThread = null;
    
        /**
         * 异步执行命令
         * 
         * @param cmd
         * @param handler
         * @throws RemoteAccessException
         */
        public void execAsync(final String cmd, final AsyncCmdCallBack callBack) throws RemoteAccessException {
            ChannelExec channelExec = getChannel(cmd);
            stopLastCmdThread();
            cmdThread = new ExecAsyncCmdThread(channelExec, callBack);
            cmdThread.setDaemon(true);
            cmdThread.start();
        }
    
        private ChannelExec getChannel(String cmd) throws RemoteAccessException {
            conn();
            try {
                ChannelExec channel = (ChannelExec) session.openChannel("exec");
                channel.setCommand(cmd);
                channel.setInputStream(null);
                channel.setErrStream(System.err);
                return channel;
            } catch (JSchException e) {
                throw new RemoteAccessException(e);
            }
        }
    
        class ExecAsyncCmdThread extends Thread {
    
            private AsyncCmdCallBack callBack;
            private ChannelExec channelExec = null;
            private volatile boolean isRunning = false;
    
            public ExecAsyncCmdThread(final ChannelExec channelExec, AsyncCmdCallBack callBack) {
                this.channelExec = channelExec;
                this.callBack = callBack;
            }
    
            @Override
            public void run() {
                InputStream inStream = null;
                String line;
                BufferedReader reader = null;
                channelExec.setPty(true);
                try {
                    inStream = channelExec.getInputStream();
                    channelExec.connect();
                    isRunning = true;
                    reader = IOHelper.toBufferedReader(inStream, StandardCharsets.UTF_8.name());
                    while (isRunning) {
                        while ((line = reader.readLine()) != null) {
                            callBack.hanndle(line);
                        }
                        if (channelExec.isClosed()) {
                            int res = channelExec.getExitStatus();
                            isRunning = false;
                            System.out.println(String.format("Exit-status: %d,thread:%s", res, Thread.currentThread()
                                    .getId()));
                            break;
                        }
                    }
                } catch (Exception e) {
                    logger.error("", e);
                } finally {
                    IOHelper.closeQuietly(reader);
                    if (channelExec != null) {
                        channelExec.disconnect();
                    }
                    isRunning = false;
                }
            }
    
            public void close() {
                channelExec.disconnect();
                isRunning = false;
            }
    
        }
    
        void stopLastCmdThread() {
            if (cmdThread != null) {
                cmdThread.close();
            }
        }
    
        @Override
        public void close() throws IOException {
            stopLastCmdThread();
            cmdThread = null;
            super.close();
        }
    
    }

     

    SFTP和SSH的java实现

 第四步:在需要使用的地方,调用此两个类即可

获取项目源码

 https://github.com/BigDataAiZq/logviewer

上一篇:vscode远程配置ssh,连接成功后,后面再失败的问题解决


下一篇:61:权限提升-Redis&Postgre&令牌窃取&进程注入;令牌窃取相当于窃取session-token;进程注入提权就是利用system权限的进程“附身”