SpringBoot使用JSch操作Linux

打印 上一主题 下一主题

主题 891|帖子 891|积分 2683


  • 推荐使用Hutool的Jsch工具包(它用的连接池的技术)
一、SSH远程连接服务器

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

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


  • JNA
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年(本文使用)
  1.     // jsch包
  2.     implementation 'com.jcraft:jsch:0.1.55'
复制代码

4.2、Jsch工具类
  1. package com.cc.jschdemo.utils;
  2. import com.jcraft.jsch.*;
  3. import lombok.Data;
  4. import org.apache.commons.lang3.StringUtils;
  5. import org.springframework.http.HttpHeaders;
  6. import org.springframework.stereotype.Component;
  7. import javax.annotation.PreDestroy;
  8. import javax.servlet.ServletOutputStream;
  9. import javax.servlet.http.HttpServletResponse;
  10. import java.io.*;
  11. import java.net.URLEncoder;
  12. import java.nio.charset.StandardCharsets;
  13. import java.util.*;
  14. /**
  15. * <p>JSch工具类</p>
  16. * <li>交给spring管理:每个使用的地方都是单例,都是单独的这个类。(new 也可以)</li>
  17. *
  18. * <li>所有方法都没有关闭(连接、会话),需要使用方自己关闭</li>
  19. *
  20. * @author CC
  21. * @since 2023/11/8
  22. */
  23. @Data
  24. @Component
  25. public class JSchUtil {
  26.     //缓存session会话
  27.     private Session session;
  28.     //通道:执行命令
  29.     private ChannelExec channelExec;
  30.     //通道:SFTP
  31.     private ChannelSftp channelSftp;
  32.     //通道:执行复杂Shell命令
  33.     private ChannelShell channelShell;
  34.     //登陆Linux服务器
  35.     public void loginLinux(String username, String password, String host, Integer port) {
  36.         try {
  37.             //每次都会重新初始化session
  38.             if (Objects.isNull(session) || !session.isConnected()) {
  39.                 JSch jsch = new JSch();
  40.                 session = jsch.getSession(username, host, port);
  41.                 session.setPassword(password);
  42.                 // 配置Session参数
  43.                 Properties config = new Properties();
  44.                 // 不进行公钥的检查
  45.                 config.put("StrictHostKeyChecking", "no");
  46.                 session.setConfig(config);
  47.                 // 设置连接超时时间(s/秒)
  48.                 session.setTimeout(300);
  49.             }
  50.             if (!session.isConnected()) {
  51.                 // 连接到远程服务器
  52.                 session.connect();
  53.             }
  54.         }catch(Exception e){
  55.             throw new RuntimeException("连接Linux失败:" + e.getMessage());
  56.         }
  57.     }
  58.     //执行命令:可以多次执行,然后必须调用关闭接口
  59.     public String executeCommand(String command) {
  60.         StringBuilder result = new StringBuilder();
  61.         BufferedReader buf = null;
  62.         try {
  63.             //每次执行都创建新的通道
  64.             channelExec = (ChannelExec) session.openChannel("exec");
  65.             channelExec.setCommand(command);
  66.             //正确的流中没有数据就走错误流中去拿。
  67.             InputStream in = channelExec.getInputStream();
  68.             InputStream errStream = channelExec.getErrStream();
  69.             channelExec.connect();
  70.             buf = new BufferedReader(new InputStreamReader(in));
  71.             String msg;
  72.             while ((msg = buf.readLine()) != null) {
  73.                 result.append(msg);
  74.             }
  75.             if (StringUtils.isBlank(result.toString())) {
  76.                 buf = new BufferedReader(new InputStreamReader(errStream));
  77.                 String msgErr;
  78.                 while ((msgErr = buf.readLine()) != null) {
  79.                     result.append(msgErr);
  80.                 }
  81.             }
  82.         }catch(Exception e){
  83.             throw new RuntimeException("关闭连接失败(执行命令):" + e.getMessage());
  84.         }finally {
  85.             if (Objects.nonNull(buf)) {
  86.                 try {
  87.                     buf.close();
  88.                 }catch(Exception e){
  89.                     e.printStackTrace();
  90.                 }
  91.             }
  92.         }
  93.         return result.toString();
  94.     }
  95.     /**
  96.      * 执行复杂shell命令
  97.      *
  98.      * @param cmds 多条命令
  99.      * @return 执行结果
  100.      * @throws Exception 连接异常
  101.      */
  102.     public String execCmdByShell(List<String> cmds) {
  103.         String result = "";
  104.         try {
  105.             channelShell = (ChannelShell) session.openChannel("shell");
  106.             InputStream inputStream = channelShell.getInputStream();
  107.             channelShell.setPty(true);
  108.             channelShell.connect();
  109.             OutputStream outputStream = channelShell.getOutputStream();
  110.             PrintWriter printWriter = new PrintWriter(outputStream);
  111.             for (String cmd : cmds) {
  112.                 printWriter.println(cmd);
  113.             }
  114.             printWriter.flush();
  115.             byte[] tmp = new byte[1024];
  116.             while (true) {
  117.                 while (inputStream.available() > 0) {
  118.                     int i = inputStream.read(tmp, 0, 1024);
  119.                     if (i < 0) {
  120.                         break;
  121.                     }
  122.                     String s = new String(tmp, 0, i);
  123.                     if (s.contains("--More--")) {
  124.                         outputStream.write((" ").getBytes());
  125.                         outputStream.flush();
  126.                     }
  127.                     System.out.println(s);
  128.                 }
  129.                 if (channelShell.isClosed()) {
  130.                     System.out.println("exit-status:" + channelShell.getExitStatus());
  131.                     break;
  132.                 }
  133.                 //间隔1s后再执行
  134.                 try {
  135.                     Thread.sleep(1000);
  136.                 } catch (Exception e) {
  137.                     e.printStackTrace();
  138.                 }
  139.             }
  140.             outputStream.close();
  141.             inputStream.close();
  142.         }catch(Exception e){
  143.             e.printStackTrace();
  144.         }
  145.         return result;
  146.     }
  147.     //下载除了云服务器的文件(你自己的服务器):因为云服务器,像阿里云服务器下载文件好像是一段一段给你的,不是一起给你。
  148.     public void downloadOtherFile(String remoteFileAbsolutePath, String fileName, HttpServletResponse response) {
  149.         try {
  150.             channelSftp = (ChannelSftp) session.openChannel("sftp");
  151.             channelSftp.connect();
  152.             //获取输入流
  153.             InputStream inputStream = channelSftp.get(remoteFileAbsolutePath);
  154.             //直接下载到本地文件
  155. //            channelSftp.get(remoteFileAbsolutePath, "D:\\Develop\\Test\\studio-3t-x64.zip");
  156.             response.setCharacterEncoding(StandardCharsets.UTF_8.name());
  157.             response.setContentType("application/octet-stream;charset=".concat(StandardCharsets.UTF_8.name()));
  158.             response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.CONTENT_DISPOSITION);
  159.             response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
  160.                     "attachment; filename=".concat(
  161.                             URLEncoder.encode(fileName, StandardCharsets.UTF_8.name())
  162.                     ));
  163.             ServletOutputStream out = response.getOutputStream();
  164.             // 从InputStream输入流读取数据 并写入到ServletOutputStream输出流
  165.             byte[] buffer = new byte[4096];
  166.             int bytesRead;
  167.             while ((bytesRead = inputStream.read(buffer)) != -1) {
  168.                 out.write(buffer, 0, bytesRead);
  169.             }
  170.             out.flush();
  171.             out.close();
  172.         }catch(Exception e){
  173.             throw new RuntimeException("关闭连接失败(下载文件):" + e.getMessage());
  174.         }
  175.     }
  176.     //下载云服务器的文件(因为云服务器传文件是一段一段的,所以不能直接像操作我们的服务器一样直接下载)(阿里云为例)
  177.     public void downloadCloudServerFile(String remoteFileAbsolutePath, String fileName, HttpServletResponse response) {
  178.         try {
  179.             channelSftp = (ChannelSftp) session.openChannel("sftp");
  180.             channelSftp.connect();
  181.             //获取输入流
  182.             InputStream inputStream = channelSftp.get(remoteFileAbsolutePath);
  183.             //阿里云应该是断点续传,后面研究……
  184.         }catch(Exception e){
  185.             throw new RuntimeException("关闭连接失败(下载文件):" + e.getMessage());
  186.         }
  187.     }
  188.     //ls命令:获取文件夹的信息
  189.     public String ls(String path){
  190.         StringBuilder sb = new StringBuilder();
  191.         try {
  192.             channelSftp = (ChannelSftp) session.openChannel("sftp");
  193.             channelSftp.connect();
  194.             Vector ls = channelSftp.ls(path);
  195.             Iterator iterator = ls.iterator();
  196.             while (iterator.hasNext()) {
  197.                 Object next = iterator.next();
  198.                 System.out.println(next);
  199.                 sb.append(next);
  200.             }
  201.         } catch (Exception e){
  202.             throw new RuntimeException(e.getMessage());
  203.         }
  204.         return sb.toString();
  205.     }
  206.     //关闭通道:释放资源
  207.     private void closeChannel(){
  208.         //不为空,且已经连接:关闭
  209.         if (Objects.nonNull(channelExec)) {
  210.             channelExec.disconnect();
  211.         }
  212.         if (Objects.nonNull(channelSftp)) {
  213.             channelSftp.disconnect();
  214.         }
  215.         if (Objects.nonNull(channelShell)) {
  216.             channelShell.disconnect();
  217.         }
  218.     }
  219.     /** 关闭通道、关闭会话:释放资源
  220.      * spring销毁前,关闭 所有会话 及 所有通道
  221.      */
  222.     @PreDestroy
  223.     public void closeAll(){
  224.         System.out.println("我被销毁了。。。。。。。。。。。。。。。。。。。。。。");
  225.         this.closeChannel();
  226.         if (Objects.nonNull(session) && session.isConnected()) {
  227.             session.disconnect();
  228.         }
  229.     }
  230. }
复制代码
4.2、使用Jsch工具类:执行命令

4.2.1、执行简单命令
  1. package com.cc.jschdemo.web.controller;
  2. import com.cc.jschdemo.utils.JSchUtil;
  3. import com.jcraft.jsch.ChannelExec;
  4. import org.springframework.web.bind.annotation.GetMapping;
  5. import org.springframework.web.bind.annotation.RequestMapping;
  6. import org.springframework.web.bind.annotation.RequestParam;
  7. import org.springframework.web.bind.annotation.RestController;
  8. import javax.annotation.Resource;
  9. import java.io.InputStream;
  10. import java.util.Arrays;
  11. /**
  12. * <p></p>
  13. *
  14. * @author CC
  15. * @since 2023/11/8
  16. */
  17. @RestController
  18. @RequestMapping("/jsch")
  19. public class JSchController {
  20.     @Resource
  21.     private JSchUtil jSchUtil;
  22.     /** <p>执行命令<p>
  23.      **/
  24.     @GetMapping
  25.     public String executeCommand() {
  26.         //登陆(默认只连接5分钟,5分钟后销毁)
  27.         jSchUtil.loginLinux("服务器账号", "服务器密码", "服务器IP", 服务器端口);
  28.         //一、执行命令
  29.         String mkdir = jSchUtil.executeCommand("mkdir ccc");
  30.         String docker = jSchUtil.executeCommand("docker");
  31.         String dockerPs = jSchUtil.executeCommand("docker ps");
  32.         System.out.println(mkdir);
  33.         System.out.println(docker);
  34.         System.out.println(dockerPs);
  35.         //执行完,关闭连接
  36.         jSchUtil.closeAll();
  37.         return docker;
  38.     }
  39. }
复制代码

  • 结果:
  • 多了一个文件夹

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

  • 结果

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

4.3.1、普通服务器下载
  1.     //下载普通服务器的文件
  2.     @PutMapping
  3.     public void downloadOtherFile(HttpServletResponse response) {
  4.         //登陆(默认只连接5分钟,5分钟后销毁)
  5.         jSchUtil.loginLinux("root", "pwd@20180808", "172.16.0.16", 22);
  6.         //下载文件
  7.         jSchUtil.downloadOtherFile(
  8.                 "/dev/libbb/studio-3t-x64.zip",
  9.                 "studio-3t-x64.zip",
  10.                 response
  11.         );
  12.         //执行完,关闭连接
  13.         jSchUtil.closeAll();
  14.     }
复制代码
4.3.2、阿里云服务器下载


  • 博主很懒,没留下什么……
五、Hutool工具封装的JSch(推荐)


  • Hutool使用的是JSch连接池,推荐使用……
  • 博主比较懒,还没实现……
六、总结

参考:
https://www.jb51.net/article/264152.htm
https://www.jb51.net/article/264148.htm

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

泉缘泉

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表