SpringBoot使用JSch操作Linux的方法

 更新时间:2023年11月29日 14:36:00   作者:C_C_菜园  
JSch是一个Java库,它提供了SSH(Secure Shell)的Java实现,允许Java程序通过SSH协议连接到远程系统(如Linux),这篇文章主要介绍了SpringBoot使用JSch操作Linux,需要的朋友可以参考下

推荐使用Hutool的Jsch工具包(它用的连接池的技术)

一、SSH远程连接服务器

SSH更多见:http://t.csdnimg.cn/PrsNv

推荐连接工具:FinalShell、Xshell、secureCRT、PuTTY
https://www.jb51.net/article/232575.htm

1、SSH(Secure Shell)主要有两大功能

  • 1、远程命令执行:SSH允许用户在远程主机上执行命令。用户可以通过SSH连接到远程主机,然后在命令行界面输入命令,就像直接在远程主机的控制台上操作一样。这是SSH最常用的功能,它使得用户可以方便地管理和维护远程主机。
  • 2、安全的文件传输:SSH提供了SFTP(SSH File Transfer Protocol)和SCP(Secure Copy)两种文件传输协议,用于在本地主机和远程主机之间安全地传输文件。这两种协议都使用SSH的安全机制,可以保护文件在传输过程中的安全性和完整性。
  • 3、除了这两大功能,SSH还有一些其他的功能,例如端口转发和动态端口转发(也称为SOCKS代理),它们可以用来建立安全的网络连接,或者绕过网络限制。

二、JNA、Process和JSchJNA

JNA主要用于在Java程序中调用本地库的函数,而不是用于远程连接到其他系统。如果你的Java程序正在Linux系统上运行,你可以使用JNA来调用Linux的本地库函数,包括那些可以获取文件和文件夹路径的函数。然而,如果你的Java程序正在一个系统(如Windows)上运行,你不能使用JNA来连接到另一个系统(如Linux)。

  • JSch

如果你需要从一个运行在Windows上的Java程序连接到一个Linux系统,你可能需要使用其他的工具或库。例如,你可以使用SSH(安全壳层)来远程连接到Linux系统,然后执行命令来获取文件和文件夹的路径。在Java中,有一些库可以帮助你使用SSH,如JSch和Apache MINA SSHD。

简单理解JNA、Process和JSch

  • JNA

(Java Native Access)和Process类都是Java中与本地系统交互的工具。JNA允许Java代码直接调用本地(C/C++)库的函数,而Process类则允许Java代码启动和控制操作系统的进程,例如执行shell命令。(JNA和Process是Java调用系统(Windows、Linux等)的本地函数,或者三方程序)

JSch是一个Java库,它提供了SSH(Secure Shell)的Java实现,允许Java程序通过SSH协议连接到远程系统(如Linux)。一旦连接成功,你可以通过JSch执行远程命令,上传和下载文件,就像直接在远程系统上操作一样。(JSch则是Java连接系统(Windows、Linux等)的工具,比如连接上Linux后,相当于直接操作Linux一样)

三、Java使用SSH的包

3.1、JSch和Apache MINA SSHD

JSch和Apache MINA SSHD都是优秀的SSH库,它们各有优点,选择哪一个主要取决于你的具体需求。

JSch是一个成熟且广泛使用的库,它提供了SSH2的完整实现,包括SFTP,SCP,端口转发等功能。JSch的API相对简单,易于使用,而且JSch的社区活跃,有大量的教程和示例代码可供参考。

Apache MINA SSHD则是一个更现代的库,它基于Apache MINA,一个高性能的网络应用框架。MINA SSHD提供了SSH2的完整实现,包括SFTP,SCP,端口转发等功能。MINA SSHD的API设计更现代,更符合Java的编程习惯,而且MINA SSHD支持异步非阻塞IO,对于需要处理大量并发连接的应用来说,可能会有更好的性能。

总的来说,如果你需要一个简单易用,社区支持好的SSH库,JSch可能是一个不错的选择。如果你需要一个设计现代,支持异步非阻塞IO的SSH库,或者你已经在使用Apache MINA,那么MINA SSHD可能更适合你。

3.2、JSch的四种认证机制:

  • 密码(本文使用):这是最常见的身份验证方式,用户需要提供用户名和密码来进行身份验证。
  • 公钥:在这种方式中,用户需要提供一个私钥,JSch会使用这个私钥来进行身份验证。这种方式通常比基于密码的身份验证更安全,因为私钥通常比密码更难被猜测或者破解。
  • 键盘交互:这种方式允许服务器发送一个或多个提问给客户端,客户端需要回答这些问题来进行身份验证。这种方式可以用来实现一些复杂的身份验证流程,例如一次性密码,或者多因素身份验证。
  • GSSAPI:GSSAPI是一种用于安全通信的API,它支持各种不同的身份验证机制,例如Kerberos。JSch可以使用GSSAPI来进行身份验证,但这需要额外的库支持。

四、JSch实现登录Linux,远程命令执行、SFTP下载和上传文件

4.1、导包Jsch官方的包上次更新18年(本文使用)

// jsch包
    implementation 'com.jcraft:jsch:0.1.55'

长期维护的jsch:https://github.com/mwiede/jsch

4.2、Jsch工具类

package com.cc.jschdemo.utils;
import com.jcraft.jsch.*;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
 * <p>JSch工具类</p>
 * <li>交给spring管理:每个使用的地方都是单例,都是单独的这个类。(new 也可以)</li>
 *
 * <li>所有方法都没有关闭(连接、会话),需要使用方自己关闭</li>
 *
 * @author CC
 * @since 2023/11/8
 */
@Data
@Component
public class JSchUtil {
    //缓存session会话
    private Session session;
    //通道:执行命令
    private ChannelExec channelExec;
    //通道:SFTP
    private ChannelSftp channelSftp;
    //通道:执行复杂Shell命令
    private ChannelShell channelShell;
    //登陆Linux服务器
    public void loginLinux(String username, String password, String host, Integer port) {
        try {
            //每次都会重新初始化session
            if (Objects.isNull(session) || !session.isConnected()) {
                JSch jsch = new JSch();
                session = jsch.getSession(username, host, port);
                session.setPassword(password);
                // 配置Session参数
                Properties config = new Properties();
                // 不进行公钥的检查
                config.put("StrictHostKeyChecking", "no");
                session.setConfig(config);
                // 设置连接超时时间(s/秒)
                session.setTimeout(300);
            }
            if (!session.isConnected()) {
                // 连接到远程服务器
                session.connect();
            }
        }catch(Exception e){
            throw new RuntimeException("连接Linux失败:" + e.getMessage());
        }
    }
    //执行命令:可以多次执行,然后必须调用关闭接口
    public String executeCommand(String command) {
        StringBuilder result = new StringBuilder();
        BufferedReader buf = null;
        try {
            //每次执行都创建新的通道
            channelExec = (ChannelExec) session.openChannel("exec");
            channelExec.setCommand(command);
            //正确的流中没有数据就走错误流中去拿。
            InputStream in = channelExec.getInputStream();
            InputStream errStream = channelExec.getErrStream();
            channelExec.connect();
            buf = new BufferedReader(new InputStreamReader(in));
            String msg;
            while ((msg = buf.readLine()) != null) {
                result.append(msg);
            }
            if (StringUtils.isBlank(result.toString())) {
                buf = new BufferedReader(new InputStreamReader(errStream));
                String msgErr;
                while ((msgErr = buf.readLine()) != null) {
                    result.append(msgErr);
                }
            }
        }catch(Exception e){
            throw new RuntimeException("关闭连接失败(执行命令):" + e.getMessage());
        }finally {
            if (Objects.nonNull(buf)) {
                try {
                    buf.close();
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
        }
        return result.toString();
    }
    /**
     * 执行复杂shell命令
     *
     * @param cmds 多条命令
     * @return 执行结果
     * @throws Exception 连接异常
     */
    public String execCmdByShell(List<String> cmds) {
        String result = "";
        try {
            channelShell = (ChannelShell) session.openChannel("shell");
            InputStream inputStream = channelShell.getInputStream();
            channelShell.setPty(true);
            channelShell.connect();
            OutputStream outputStream = channelShell.getOutputStream();
            PrintWriter printWriter = new PrintWriter(outputStream);
            for (String cmd : cmds) {
                printWriter.println(cmd);
            }
            printWriter.flush();
            byte[] tmp = new byte[1024];
            while (true) {
                while (inputStream.available() > 0) {
                    int i = inputStream.read(tmp, 0, 1024);
                    if (i < 0) {
                        break;
                    }
                    String s = new String(tmp, 0, i);
                    if (s.contains("--More--")) {
                        outputStream.write((" ").getBytes());
                        outputStream.flush();
                    }
                    System.out.println(s);
                }
                if (channelShell.isClosed()) {
                    System.out.println("exit-status:" + channelShell.getExitStatus());
                    break;
                }
                //间隔1s后再执行
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            outputStream.close();
            inputStream.close();
        }catch(Exception e){
            e.printStackTrace();
        }
        return result;
    }
    //下载除了云服务器的文件(你自己的服务器):因为云服务器,像阿里云服务器下载文件好像是一段一段给你的,不是一起给你。
    public void downloadOtherFile(String remoteFileAbsolutePath, String fileName, HttpServletResponse response) {
        try {
            channelSftp = (ChannelSftp) session.openChannel("sftp");
            channelSftp.connect();
            //获取输入流
            InputStream inputStream = channelSftp.get(remoteFileAbsolutePath);
            //直接下载到本地文件
//            channelSftp.get(remoteFileAbsolutePath, "D:\\Develop\\Test\\studio-3t-x64.zip");
            response.setCharacterEncoding(StandardCharsets.UTF_8.name());
            response.setContentType("application/octet-stream;charset=".concat(StandardCharsets.UTF_8.name()));
            response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.CONTENT_DISPOSITION);
            response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
                    "attachment; filename=".concat(
                            URLEncoder.encode(fileName, StandardCharsets.UTF_8.name())
                    ));
            ServletOutputStream out = response.getOutputStream();
            // 从InputStream输入流读取数据 并写入到ServletOutputStream输出流
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
            }
            out.flush();
            out.close();
        }catch(Exception e){
            throw new RuntimeException("关闭连接失败(下载文件):" + e.getMessage());
        }
    }
    //下载云服务器的文件(因为云服务器传文件是一段一段的,所以不能直接像操作我们的服务器一样直接下载)(阿里云为例)
    public void downloadCloudServerFile(String remoteFileAbsolutePath, String fileName, HttpServletResponse response) {
        try {
            channelSftp = (ChannelSftp) session.openChannel("sftp");
            channelSftp.connect();
            //获取输入流
            InputStream inputStream = channelSftp.get(remoteFileAbsolutePath);
            //阿里云应该是断点续传,后面研究……
        }catch(Exception e){
            throw new RuntimeException("关闭连接失败(下载文件):" + e.getMessage());
        }
    }
    //ls命令:获取文件夹的信息
    public String ls(String path){
        StringBuilder sb = new StringBuilder();
        try {
            channelSftp = (ChannelSftp) session.openChannel("sftp");
            channelSftp.connect();
            Vector ls = channelSftp.ls(path);
            Iterator iterator = ls.iterator();
            while (iterator.hasNext()) {
                Object next = iterator.next();
                System.out.println(next);
                sb.append(next);
            }
        } catch (Exception e){
            throw new RuntimeException(e.getMessage());
        }
        return sb.toString();
    }
    //关闭通道:释放资源
    private void closeChannel(){
        //不为空,且已经连接:关闭
        if (Objects.nonNull(channelExec)) {
            channelExec.disconnect();
        }
        if (Objects.nonNull(channelSftp)) {
            channelSftp.disconnect();
        }
        if (Objects.nonNull(channelShell)) {
            channelShell.disconnect();
        }
    }
    /** 关闭通道、关闭会话:释放资源
     * spring销毁前,关闭 所有会话 及 所有通道
     */
    @PreDestroy
    public void closeAll(){
        System.out.println("我被销毁了。。。。。。。。。。。。。。。。。。。。。。");
        this.closeChannel();
        if (Objects.nonNull(session) && session.isConnected()) {
            session.disconnect();
        }
    }
}

4.2、使用Jsch工具类:执行命令

4.2.1、执行简单命令

package com.cc.jschdemo.web.controller;
import com.cc.jschdemo.utils.JSchUtil;
import com.jcraft.jsch.ChannelExec;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.io.InputStream;
import java.util.Arrays;
/**
 * <p></p>
 *
 * @author CC
 * @since 2023/11/8
 */
@RestController
@RequestMapping("/jsch")
public class JSchController {
    @Resource
    private JSchUtil jSchUtil;
    /** <p>执行命令<p>
     **/
    @GetMapping
    public String executeCommand() {
        //登陆(默认只连接5分钟,5分钟后销毁)
        jSchUtil.loginLinux("服务器账号", "服务器密码", "服务器IP", 服务器端口);
        //一、执行命令
        String mkdir = jSchUtil.executeCommand("mkdir ccc");
        String docker = jSchUtil.executeCommand("docker");
        String dockerPs = jSchUtil.executeCommand("docker ps");
        System.out.println(mkdir);
        System.out.println(docker);
        System.out.println(dockerPs);
        //执行完,关闭连接
        jSchUtil.closeAll();
        return docker;
    }
}

结果:多了一个文件夹

4.2.2、执行复杂的shell命令

   /** <p>执行命令<p>
     **/
    @PostMapping
    public String execCmdByShell() {
        //登陆(默认只连接5分钟,5分钟后销毁)
        jSchUtil.loginLinux("服务器账号", "服务器密码", "服务器IP", 服务器端口);
        //二、执行shell脚本(可以改造成传入的shell脚步)
        jSchUtil.execCmdByShell(Arrays.asList("cd /", "ll" , "cd cc/", "mkdir ccccc222", "ll"));
        //执行完,关闭连接
        jSchUtil.closeAll();
        return "docker";
    }

结果

4.3、使用Jsch工具类:下载文件

4.3.1、普通服务器下载

    //下载普通服务器的文件
    @PutMapping
    public void downloadOtherFile(HttpServletResponse response) {
        //登陆(默认只连接5分钟,5分钟后销毁)
         jSchUtil.loginLinux("服务器账号", "服务器密码", "服务器IP", 服务器端口);
        //下载文件
        jSchUtil.downloadOtherFile(
                "/dev/libbb/studio-3t-x64.zip",
                "studio-3t-x64.zip",
                response
        );
        //执行完,关闭连接
        jSchUtil.closeAll();
    }

4.3.2、阿里云服务器下载

https://www.jb51.net/article/178581.htm

五、Hutool工具封装的JSch(推荐)

Hutool使用的是JSch连接池,推荐使用……

六、总结

参考:

https://www.jb51.net/article/264152.htm

https://www.jb51.net/article/264148.htm

到此这篇关于SpringBoot使用JSch操作Linux的文章就介绍到这了,更多相关SpringBoot使用JSch内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • spring中IOC控制反转依赖注入和new对象的区别说明

    spring中IOC控制反转依赖注入和new对象的区别说明

    这篇文章主要介绍了spring中IOC控制反转依赖注入和new对象的区别说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • java输出1~100之间的全部素数的5种方式总结

    java输出1~100之间的全部素数的5种方式总结

    这篇文章主要介绍了java输出1~100之间的全部素数的5种方式总结,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • 详解Java并发编程基础之volatile

    详解Java并发编程基础之volatile

    volatile作为Java多线程中轻量级的同步措施,保证了多线程环境中“共享变量”的可见性。这里的可见性简单而言可以理解为当一个线程修改了一个共享变量的时候,另外的线程能够读到这个修改的值。本文将详解介绍Java并发编程基础之volatile
    2021-06-06
  • Json字符串与Object、List、Map的互转工具类

    Json字符串与Object、List、Map的互转工具类

    今天小编就为大家分享一篇关于Json字符串与Object、List、Map的互转工具类,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-12-12
  • Spring中基于xml的AOP实现详解

    Spring中基于xml的AOP实现详解

    这篇文章主要介绍了Spring中基于xml的AOP实现详解,基于xml与基于注解的AOP本质上是非常相似的,都是需要封装横切关注点,封装到切面中,然后把横切关注点封装为一个方法,再把该方法设置为当前的一个通知,再通过切入点表达式定位到横切点就可以了,需要的朋友可以参考下
    2023-09-09
  • 基于Feign使用okhttp的填坑之旅

    基于Feign使用okhttp的填坑之旅

    这篇文章主要介绍了基于Feign使用okhttp的填坑之旅,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02
  • Java多线程定时器Timer原理及实现

    Java多线程定时器Timer原理及实现

    这篇文章主要介绍了Java多线程定时器Timer原理及实现,涉及Timer的schedule的使用,定时器Timer的schedule等相关内容以及代码示例,具有一定参考价值,需要的朋友可以了解下。
    2017-11-11
  • IDEA类与方法注释模板设置图文教程(非常详细)

    IDEA类与方法注释模板设置图文教程(非常详细)

    IDEA自带的注释模板不是太好用,我本人到网上搜集了很多资料系统的整理了一下制作了一份比较完整的模板来分享给大家,下面这篇文章主要给大家介绍了关于IDEA类与方法注释模板设置的相关资料,需要的朋友可以参考下
    2022-09-09
  • maven依赖的version声明控制方式

    maven依赖的version声明控制方式

    这篇文章主要介绍了maven依赖的version声明控制方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • JavaWeb中的路径问题解读

    JavaWeb中的路径问题解读

    这篇文章主要介绍了JavaWeb中的路径问题解读,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-11-11

最新评论