活动公告

系统通知
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

Java实现SSH客户端功能完全指南 无需Xshell 直接通过代码建立安全连接 执行远程命令 管理服务器

SunJu_FaceMall

3万

主题

2720

科技点

3万

积分

执行版主

碾压王

积分
32881

塔罗立华奏

执行版主 发表于 2025-8-29 09:00:00 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

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

x
引言

SSH(Secure Shell)是一种加密的网络协议,用于在不安全的网络中安全地进行远程操作。通常,我们使用Xshell、PuTTY等图形化工具来连接和管理远程服务器,但在某些场景下,我们可能需要在Java应用程序中直接实现SSH功能,例如自动化运维、远程监控、批量任务执行等。

本文将详细介绍如何使用Java实现SSH客户端功能,无需依赖Xshell等第三方工具,直接通过代码建立安全连接、执行远程命令和管理服务器。我们将使用Java中最流行的SSH库——JSch(Java Secure Channel)来实现这些功能。

准备工作

1. 添加JSch依赖

首先,我们需要在项目中添加JSch库的依赖。如果你使用Maven,可以在pom.xml中添加以下依赖:
  1. <dependency>
  2.     <groupId>com.jcraft</groupId>
  3.     <artifactId>jsch</artifactId>
  4.     <version>0.1.55</version>
  5. </dependency>
复制代码

如果你使用Gradle,可以在build.gradle中添加:
  1. implementation 'com.jcraft:jsch:0.1.55'
复制代码

2. 了解SSH连接的基本要素

在开始编码之前,我们需要了解SSH连接的基本要素:

• 主机(Host):远程服务器的IP地址或域名
• 端口(Port):SSH服务监听的端口,默认为22
• 用户名(Username):登录远程服务器的用户名
• 认证方式:密码认证或公钥认证
• 超时时间:连接和操作的超时设置

建立基本SSH连接

1. 创建JSch实例并配置
  1. import com.jcraft.jsch.*;
  2. public class SSHClient {
  3.     private JSch jsch;
  4.     private Session session;
  5.     private String host;
  6.     private int port;
  7.     private String username;
  8.    
  9.     public SSHClient(String host, int port, String username) {
  10.         this.jsch = new JSch();
  11.         this.host = host;
  12.         this.port = port;
  13.         this.username = username;
  14.     }
  15.    
  16.     // 其他方法将在后面实现
  17. }
复制代码

2. 使用密码认证建立连接
  1. public void connectWithPassword(String password) throws JSchException {
  2.     // 创建会话
  3.     session = jsch.getSession(username, host, port);
  4.    
  5.     // 设置密码
  6.     session.setPassword(password);
  7.    
  8.     // 严格主机密钥检查
  9.     // 设置为"no"表示不检查,生产环境应该设置为"yes"并提供已知主机文件
  10.     session.setConfig("StrictHostKeyChecking", "no");
  11.    
  12.     // 设置超时时间
  13.     session.setTimeout(10000); // 10秒
  14.    
  15.     // 建立连接
  16.     session.connect();
  17.    
  18.     System.out.println("SSH连接成功建立");
  19. }
复制代码

3. 使用公钥认证建立连接
  1. public void connectWithPublicKey(String privateKeyPath, String passphrase) throws JSchException {
  2.     // 添加私钥
  3.     if (passphrase == null || passphrase.isEmpty()) {
  4.         jsch.addIdentity(privateKeyPath);
  5.     } else {
  6.         jsch.addIdentity(privateKeyPath, passphrase);
  7.     }
  8.    
  9.     // 创建会话
  10.     session = jsch.getSession(username, host, port);
  11.    
  12.     // 严格主机密钥检查
  13.     session.setConfig("StrictHostKeyChecking", "no");
  14.    
  15.     // 设置超时时间
  16.     session.setTimeout(10000); // 10秒
  17.    
  18.     // 建立连接
  19.     session.connect();
  20.    
  21.     System.out.println("SSH连接成功建立");
  22. }
复制代码

4. 关闭连接
  1. public void disconnect() {
  2.     if (session != null && session.isConnected()) {
  3.         session.disconnect();
  4.         System.out.println("SSH连接已关闭");
  5.     }
  6. }
复制代码

执行远程命令

1. 执行简单命令
  1. public String executeCommand(String command) throws JSchException, IOException {
  2.     if (session == null || !session.isConnected()) {
  3.         throw new IllegalStateException("SSH会话未建立");
  4.     }
  5.    
  6.     // 创建执行通道
  7.     ChannelExec channel = (ChannelExec) session.openChannel("exec");
  8.     channel.setCommand(command);
  9.    
  10.     // 获取输入流
  11.     InputStream in = channel.getInputStream();
  12.     InputStream err = channel.getExtInputStream();
  13.    
  14.     // 连接通道
  15.     channel.connect();
  16.    
  17.     // 读取输出
  18.     StringBuilder output = new StringBuilder();
  19.     StringBuilder error = new StringBuilder();
  20.    
  21.     byte[] tmp = new byte[1024];
  22.     while (true) {
  23.         while (in.available() > 0) {
  24.             int i = in.read(tmp, 0, 1024);
  25.             if (i < 0) break;
  26.             output.append(new String(tmp, 0, i));
  27.         }
  28.         while (err.available() > 0) {
  29.             int i = err.read(tmp, 0, 1024);
  30.             if (i < 0) break;
  31.             error.append(new String(tmp, 0, i));
  32.         }
  33.         if (channel.isClosed()) {
  34.             if (in.available() > 0 || err.available() > 0) continue;
  35.             System.out.println("退出状态: " + channel.getExitStatus());
  36.             break;
  37.         }
  38.         try {
  39.             Thread.sleep(1000);
  40.         } catch (Exception ee) {
  41.             ee.printStackTrace();
  42.         }
  43.     }
  44.    
  45.     // 关闭通道
  46.     channel.disconnect();
  47.    
  48.     // 如果有错误输出,抛出异常
  49.     if (error.length() > 0) {
  50.         throw new IOException("命令执行错误: " + error.toString());
  51.     }
  52.    
  53.     return output.toString();
  54. }
复制代码

2. 执行交互式命令

有些命令需要用户输入,例如确认操作或输入密码。我们可以使用ChannelShell来实现:
  1. public String executeInteractiveCommand(String command, String input) throws JSchException, IOException {
  2.     if (session == null || !session.isConnected()) {
  3.         throw new IllegalStateException("SSH会话未建立");
  4.     }
  5.    
  6.     // 创建Shell通道
  7.     ChannelShell channel = (ChannelShell) session.openChannel("shell");
  8.    
  9.     // 获取输入输出流
  10.     OutputStream out = channel.getOutputStream();
  11.     InputStream in = channel.getInputStream();
  12.    
  13.     // 连接通道
  14.     channel.connect();
  15.    
  16.     // 发送命令
  17.     out.write((command + "\n").getBytes());
  18.     out.flush();
  19.    
  20.     // 如果需要输入,发送输入
  21.     if (input != null && !input.isEmpty()) {
  22.         out.write((input + "\n").getBytes());
  23.         out.flush();
  24.     }
  25.    
  26.     // 读取输出
  27.     StringBuilder output = new StringBuilder();
  28.     byte[] tmp = new byte[1024];
  29.     while (true) {
  30.         while (in.available() > 0) {
  31.             int i = in.read(tmp, 0, 1024);
  32.             if (i < 0) break;
  33.             output.append(new String(tmp, 0, i));
  34.         }
  35.         if (channel.isClosed()) {
  36.             if (in.available() > 0) continue;
  37.             System.out.println("退出状态: " + channel.getExitStatus());
  38.             break;
  39.         }
  40.         try {
  41.             Thread.sleep(1000);
  42.         } catch (Exception ee) {
  43.             ee.printStackTrace();
  44.         }
  45.     }
  46.    
  47.     // 关闭通道
  48.     channel.disconnect();
  49.    
  50.     return output.toString();
  51. }
复制代码

3. 执行长时间运行的命令

对于长时间运行的命令,我们可能需要实时获取输出并能够随时终止命令:
  1. public void executeLongRunningCommand(String command, CommandOutputListener listener) throws JSchException, IOException {
  2.     if (session == null || !session.isConnected()) {
  3.         throw new IllegalStateException("SSH会话未建立");
  4.     }
  5.    
  6.     // 创建执行通道
  7.     ChannelExec channel = (ChannelExec) session.openChannel("exec");
  8.     channel.setCommand(command);
  9.    
  10.     // 获取输入流
  11.     InputStream in = channel.getInputStream();
  12.     InputStream err = channel.getExtInputStream();
  13.    
  14.     // 连接通道
  15.     channel.connect();
  16.    
  17.     // 创建线程读取输出
  18.     Thread outputThread = new Thread(() -> {
  19.         try {
  20.             byte[] tmp = new byte[1024];
  21.             while (true) {
  22.                 while (in.available() > 0) {
  23.                     int i = in.read(tmp, 0, 1024);
  24.                     if (i < 0) break;
  25.                     if (listener != null) {
  26.                         listener.onOutput(new String(tmp, 0, i));
  27.                     }
  28.                 }
  29.                 if (channel.isClosed()) {
  30.                     if (in.available() > 0) continue;
  31.                     if (listener != null) {
  32.                         listener.onExit(channel.getExitStatus());
  33.                     }
  34.                     break;
  35.                 }
  36.                 try {
  37.                     Thread.sleep(100);
  38.                 } catch (Exception ee) {
  39.                     ee.printStackTrace();
  40.                 }
  41.             }
  42.         } catch (IOException e) {
  43.             e.printStackTrace();
  44.         }
  45.     });
  46.    
  47.     // 创建线程读取错误
  48.     Thread errorThread = new Thread(() -> {
  49.         try {
  50.             byte[] tmp = new byte[1024];
  51.             while (true) {
  52.                 while (err.available() > 0) {
  53.                     int i = err.read(tmp, 0, 1024);
  54.                     if (i < 0) break;
  55.                     if (listener != null) {
  56.                         listener.onError(new String(tmp, 0, i));
  57.                     }
  58.                 }
  59.                 if (channel.isClosed()) {
  60.                     break;
  61.                 }
  62.                 try {
  63.                     Thread.sleep(100);
  64.                 } catch (Exception ee) {
  65.                     ee.printStackTrace();
  66.                 }
  67.             }
  68.         } catch (IOException e) {
  69.             e.printStackTrace();
  70.         }
  71.     });
  72.    
  73.     // 启动线程
  74.     outputThread.start();
  75.     errorThread.start();
  76.    
  77.     // 等待线程结束
  78.     try {
  79.         outputThread.join();
  80.         errorThread.join();
  81.     } catch (InterruptedException e) {
  82.         e.printStackTrace();
  83.     }
  84.    
  85.     // 关闭通道
  86.     channel.disconnect();
  87. }
  88. // 命令输出监听器接口
  89. public interface CommandOutputListener {
  90.     void onOutput(String output);
  91.     void onError(String error);
  92.     void onExit(int exitStatus);
  93. }
复制代码

文件传输

1. 上传文件
  1. public void uploadFile(String localFile, String remoteFile) throws JSchException, SftpException {
  2.     if (session == null || !session.isConnected()) {
  3.         throw new IllegalStateException("SSH会话未建立");
  4.     }
  5.    
  6.     // 创建SFTP通道
  7.     ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
  8.     channel.connect();
  9.    
  10.     try {
  11.         // 上传文件
  12.         channel.put(localFile, remoteFile);
  13.         System.out.println("文件上传成功: " + localFile + " -> " + remoteFile);
  14.     } finally {
  15.         // 关闭通道
  16.         channel.disconnect();
  17.     }
  18. }
复制代码

2. 下载文件
  1. public void downloadFile(String remoteFile, String localFile) throws JSchException, SftpException {
  2.     if (session == null || !session.isConnected()) {
  3.         throw new IllegalStateException("SSH会话未建立");
  4.     }
  5.    
  6.     // 创建SFTP通道
  7.     ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
  8.     channel.connect();
  9.    
  10.     try {
  11.         // 下载文件
  12.         channel.get(remoteFile, localFile);
  13.         System.out.println("文件下载成功: " + remoteFile + " -> " + localFile);
  14.     } finally {
  15.         // 关闭通道
  16.         channel.disconnect();
  17.     }
  18. }
复制代码

3. 上传目录
  1. public void uploadDirectory(String localDir, String remoteDir) throws JSchException, SftpException {
  2.     if (session == null || !session.isConnected()) {
  3.         throw new IllegalStateException("SSH会话未建立");
  4.     }
  5.    
  6.     // 创建SFTP通道
  7.     ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
  8.     channel.connect();
  9.    
  10.     try {
  11.         // 创建远程目录
  12.         try {
  13.             channel.mkdir(remoteDir);
  14.         } catch (SftpException e) {
  15.             // 目录可能已存在,忽略错误
  16.             if (e.id != ChannelSftp.SSH_FX_FAILURE) {
  17.                 throw e;
  18.             }
  19.         }
  20.         
  21.         // 获取本地文件列表
  22.         File localDirFile = new File(localDir);
  23.         File[] files = localDirFile.listFiles();
  24.         if (files != null) {
  25.             for (File file : files) {
  26.                 if (file.isDirectory()) {
  27.                     // 递归上传子目录
  28.                     uploadDirectory(file.getAbsolutePath(), remoteDir + "/" + file.getName());
  29.                 } else {
  30.                     // 上传文件
  31.                     channel.put(file.getAbsolutePath(), remoteDir + "/" + file.getName());
  32.                     System.out.println("文件上传成功: " + file.getAbsolutePath() + " -> " + remoteDir + "/" + file.getName());
  33.                 }
  34.             }
  35.         }
  36.     } finally {
  37.         // 关闭通道
  38.         channel.disconnect();
  39.     }
  40. }
复制代码

4. 下载目录
  1. public void downloadDirectory(String remoteDir, String localDir) throws JSchException, SftpException {
  2.     if (session == null || !session.isConnected()) {
  3.         throw new IllegalStateException("SSH会话未建立");
  4.     }
  5.    
  6.     // 创建SFTP通道
  7.     ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
  8.     channel.connect();
  9.    
  10.     try {
  11.         // 创建本地目录
  12.         File localDirFile = new File(localDir);
  13.         if (!localDirFile.exists()) {
  14.             localDirFile.mkdirs();
  15.         }
  16.         
  17.         // 获取远程文件列表
  18.         Vector<ChannelSftp.LsEntry> files = channel.ls(remoteDir);
  19.         for (ChannelSftp.LsEntry file : files) {
  20.             String fileName = file.getFilename();
  21.             if (".".equals(fileName) || "..".equals(fileName)) {
  22.                 continue;
  23.             }
  24.             
  25.             String remotePath = remoteDir + "/" + fileName;
  26.             String localPath = localDir + "/" + fileName;
  27.             
  28.             if (file.getAttrs().isDir()) {
  29.                 // 递归下载子目录
  30.                 downloadDirectory(remotePath, localPath);
  31.             } else {
  32.                 // 下载文件
  33.                 channel.get(remotePath, localPath);
  34.                 System.out.println("文件下载成功: " + remotePath + " -> " + localPath);
  35.             }
  36.         }
  37.     } finally {
  38.         // 关闭通道
  39.         channel.disconnect();
  40.     }
  41. }
复制代码

高级功能

1. 端口转发

SSH端口转发允许我们将本地端口映射到远程服务器的端口,或者将远程服务器的端口映射到本地端口。
  1. public void setLocalPortForward(int localPort, String remoteHost, int remotePort) throws JSchException {
  2.     if (session == null || !session.isConnected()) {
  3.         throw new IllegalStateException("SSH会话未建立");
  4.     }
  5.    
  6.     // 设置本地端口转发
  7.     session.setPortForwardingL(localPort, remoteHost, remotePort);
  8.     System.out.println("本地端口转发设置成功: localhost:" + localPort + " -> " + remoteHost + ":" + remotePort);
  9. }
复制代码
  1. public void setRemotePortForward(int remotePort, String localHost, int localPort) throws JSchException {
  2.     if (session == null || !session.isConnected()) {
  3.         throw new IllegalStateException("SSH会话未建立");
  4.     }
  5.    
  6.     // 设置远程端口转发
  7.     session.setPortForwardingR(remotePort, localHost, localPort);
  8.     System.out.println("远程端口转发设置成功: " + host + ":" + remotePort + " -> " + localHost + ":" + localPort);
  9. }
复制代码

2. 动态端口转发(SOCKS代理)
  1. public void setDynamicPortForward(int localPort) throws JSchException {
  2.     if (session == null || !session.isConnected()) {
  3.         throw new IllegalStateException("SSH会话未建立");
  4.     }
  5.    
  6.     // 设置动态端口转发(SOCKS代理)
  7.     session.setPortForwardingL(localPort, "", 0);
  8.     System.out.println("动态端口转发设置成功: localhost:" + localPort + " (SOCKS代理)");
  9. }
复制代码

3. X11转发
  1. public void enableX11Forwarding() throws JSchException {
  2.     if (session == null || !session.isConnected()) {
  3.         throw new IllegalStateException("SSH会话未建立");
  4.     }
  5.    
  6.     // 启用X11转发
  7.     session.setX11Host("localhost");
  8.     session.setX11Port(6000);
  9.     session.setX11Cookie(new File("/tmp/.X11-unix/X0"));
  10.    
  11.     System.out.println("X11转发已启用");
  12. }
复制代码

4. 代理跳转
  1. public void connectViaProxy(String proxyHost, int proxyPort, String proxyUsername,
  2.                           String proxyPassword, String targetHost, int targetPort,
  3.                           String targetUsername, String targetPassword) throws JSchException {
  4.    
  5.     // 创建到代理服务器的会话
  6.     Session proxySession = jsch.getSession(proxyUsername, proxyHost, proxyPort);
  7.     proxySession.setPassword(proxyPassword);
  8.     proxySession.setConfig("StrictHostKeyChecking", "no");
  9.     proxySession.connect();
  10.    
  11.     try {
  12.         // 创建到目标服务器的会话
  13.         Session targetSession = jsch.getSession(targetUsername, targetHost, targetPort);
  14.         targetSession.setPassword(targetPassword);
  15.         targetSession.setConfig("StrictHostKeyChecking", "no");
  16.         
  17.         // 设置代理跳转
  18.         targetSession.setProxy(new ProxyHTTP(proxyHost, proxyPort));
  19.         
  20.         // 连接到目标服务器
  21.         targetSession.connect();
  22.         
  23.         // 保存目标会话
  24.         this.session = targetSession;
  25.         
  26.         System.out.println("通过代理跳转连接成功");
  27.     } catch (JSchException e) {
  28.         // 如果连接失败,关闭代理会话
  29.         proxySession.disconnect();
  30.         throw e;
  31.     }
  32. }
复制代码

完整示例

下面是一个完整的SSH客户端实现,集成了我们前面讨论的所有功能:
  1. import com.jcraft.jsch.*;
  2. import java.io.*;
  3. import java.util.Vector;
  4. public class AdvancedSSHClient {
  5.     private JSch jsch;
  6.     private Session session;
  7.     private String host;
  8.     private int port;
  9.     private String username;
  10.    
  11.     public AdvancedSSHClient(String host, int port, String username) {
  12.         this.jsch = new JSch();
  13.         this.host = host;
  14.         this.port = port;
  15.         this.username = username;
  16.     }
  17.    
  18.     // 连接方法
  19.     public void connectWithPassword(String password) throws JSchException {
  20.         session = jsch.getSession(username, host, port);
  21.         session.setPassword(password);
  22.         session.setConfig("StrictHostKeyChecking", "no");
  23.         session.setTimeout(10000);
  24.         session.connect();
  25.         System.out.println("SSH连接成功建立");
  26.     }
  27.    
  28.     public void connectWithPublicKey(String privateKeyPath, String passphrase) throws JSchException {
  29.         if (passphrase == null || passphrase.isEmpty()) {
  30.             jsch.addIdentity(privateKeyPath);
  31.         } else {
  32.             jsch.addIdentity(privateKeyPath, passphrase);
  33.         }
  34.         
  35.         session = jsch.getSession(username, host, port);
  36.         session.setConfig("StrictHostKeyChecking", "no");
  37.         session.setTimeout(10000);
  38.         session.connect();
  39.         System.out.println("SSH连接成功建立");
  40.     }
  41.    
  42.     // 断开连接
  43.     public void disconnect() {
  44.         if (session != null && session.isConnected()) {
  45.             session.disconnect();
  46.             System.out.println("SSH连接已关闭");
  47.         }
  48.     }
  49.    
  50.     // 执行命令
  51.     public String executeCommand(String command) throws JSchException, IOException {
  52.         if (session == null || !session.isConnected()) {
  53.             throw new IllegalStateException("SSH会话未建立");
  54.         }
  55.         
  56.         ChannelExec channel = (ChannelExec) session.openChannel("exec");
  57.         channel.setCommand(command);
  58.         
  59.         InputStream in = channel.getInputStream();
  60.         InputStream err = channel.getExtInputStream();
  61.         channel.connect();
  62.         
  63.         StringBuilder output = new StringBuilder();
  64.         StringBuilder error = new StringBuilder();
  65.         
  66.         byte[] tmp = new byte[1024];
  67.         while (true) {
  68.             while (in.available() > 0) {
  69.                 int i = in.read(tmp, 0, 1024);
  70.                 if (i < 0) break;
  71.                 output.append(new String(tmp, 0, i));
  72.             }
  73.             while (err.available() > 0) {
  74.                 int i = err.read(tmp, 0, 1024);
  75.                 if (i < 0) break;
  76.                 error.append(new String(tmp, 0, i));
  77.             }
  78.             if (channel.isClosed()) {
  79.                 if (in.available() > 0 || err.available() > 0) continue;
  80.                 System.out.println("退出状态: " + channel.getExitStatus());
  81.                 break;
  82.             }
  83.             try {
  84.                 Thread.sleep(1000);
  85.             } catch (Exception ee) {
  86.                 ee.printStackTrace();
  87.             }
  88.         }
  89.         
  90.         channel.disconnect();
  91.         
  92.         if (error.length() > 0) {
  93.             throw new IOException("命令执行错误: " + error.toString());
  94.         }
  95.         
  96.         return output.toString();
  97.     }
  98.    
  99.     // 文件传输
  100.     public void uploadFile(String localFile, String remoteFile) throws JSchException, SftpException {
  101.         if (session == null || !session.isConnected()) {
  102.             throw new IllegalStateException("SSH会话未建立");
  103.         }
  104.         
  105.         ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
  106.         channel.connect();
  107.         
  108.         try {
  109.             channel.put(localFile, remoteFile);
  110.             System.out.println("文件上传成功: " + localFile + " -> " + remoteFile);
  111.         } finally {
  112.             channel.disconnect();
  113.         }
  114.     }
  115.    
  116.     public void downloadFile(String remoteFile, String localFile) throws JSchException, SftpException {
  117.         if (session == null || !session.isConnected()) {
  118.             throw new IllegalStateException("SSH会话未建立");
  119.         }
  120.         
  121.         ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
  122.         channel.connect();
  123.         
  124.         try {
  125.             channel.get(remoteFile, localFile);
  126.             System.out.println("文件下载成功: " + remoteFile + " -> " + localFile);
  127.         } finally {
  128.             channel.disconnect();
  129.         }
  130.     }
  131.    
  132.     // 端口转发
  133.     public void setLocalPortForward(int localPort, String remoteHost, int remotePort) throws JSchException {
  134.         if (session == null || !session.isConnected()) {
  135.             throw new IllegalStateException("SSH会话未建立");
  136.         }
  137.         
  138.         session.setPortForwardingL(localPort, remoteHost, remotePort);
  139.         System.out.println("本地端口转发设置成功: localhost:" + localPort + " -> " + remoteHost + ":" + remotePort);
  140.     }
  141.    
  142.     public void setRemotePortForward(int remotePort, String localHost, int localPort) throws JSchException {
  143.         if (session == null || !session.isConnected()) {
  144.             throw new IllegalStateException("SSH会话未建立");
  145.         }
  146.         
  147.         session.setPortForwardingR(remotePort, localHost, localPort);
  148.         System.out.println("远程端口转发设置成功: " + host + ":" + remotePort + " -> " + localHost + ":" + localPort);
  149.     }
  150.    
  151.     public void setDynamicPortForward(int localPort) throws JSchException {
  152.         if (session == null || !session.isConnected()) {
  153.             throw new IllegalStateException("SSH会话未建立");
  154.         }
  155.         
  156.         session.setPortForwardingL(localPort, "", 0);
  157.         System.out.println("动态端口转发设置成功: localhost:" + localPort + " (SOCKS代理)");
  158.     }
  159.    
  160.     // 使用示例
  161.     public static void main(String[] args) {
  162.         try {
  163.             // 创建SSH客户端
  164.             AdvancedSSHClient client = new AdvancedSSHClient("example.com", 22, "username");
  165.             
  166.             // 使用密码连接
  167.             client.connectWithPassword("password");
  168.             
  169.             // 执行命令
  170.             String result = client.executeCommand("ls -l");
  171.             System.out.println("命令执行结果:");
  172.             System.out.println(result);
  173.             
  174.             // 上传文件
  175.             client.uploadFile("/path/to/local/file.txt", "/path/to/remote/file.txt");
  176.             
  177.             // 下载文件
  178.             client.downloadFile("/path/to/remote/file.txt", "/path/to/local/downloaded_file.txt");
  179.             
  180.             // 设置本地端口转发
  181.             client.setLocalPortForward(8080, "localhost", 80);
  182.             
  183.             // 断开连接
  184.             client.disconnect();
  185.         } catch (Exception e) {
  186.             e.printStackTrace();
  187.         }
  188.     }
  189. }
复制代码

最佳实践和注意事项

1. 安全性考虑

• 主机密钥验证:在生产环境中,应该启用严格的主机密钥检查,而不是简单地设置为”no”:
  1. // 使用已知主机文件
  2. jsch.setKnownHosts("/path/to/known_hosts");
复制代码

• 密码安全:避免在代码中硬编码密码,可以使用配置文件或环境变量:
  1. // 从环境变量获取密码
  2. String password = System.getenv("SSH_PASSWORD");
复制代码

• 使用公钥认证:公钥认证比密码认证更安全,建议在生产环境中使用:
  1. // 使用公钥认证
  2. client.connectWithPublicKey("/path/to/private/key", "passphrase");
复制代码

2. 错误处理

• 异常处理:正确处理JSchException和IOException,提供有意义的错误信息:
  1. try {
  2.     client.connectWithPassword(password);
  3.     String result = client.executeCommand(command);
  4.     System.out.println(result);
  5. } catch (JSchException e) {
  6.     System.err.println("SSH连接错误: " + e.getMessage());
  7. } catch (IOException e) {
  8.     System.err.println("命令执行错误: " + e.getMessage());
  9. } finally {
  10.     client.disconnect();
  11. }
复制代码

• 超时设置:为连接和操作设置合理的超时时间,避免长时间等待:
  1. // 设置连接超时
  2. session.setTimeout(10000); // 10秒
  3. // 设置通道超时
  4. channel.connect(5000); // 5秒
复制代码

3. 性能优化

• 重用会话:避免频繁创建和关闭会话,尽量重用已建立的会话:
  1. // 创建单例SSH客户端
  2. public class SSHClientManager {
  3.     private static AdvancedSSHClient client;
  4.    
  5.     public static synchronized AdvancedSSHClient getClient(String host, int port, String username, String password) throws JSchException {
  6.         if (client == null || !client.isConnected()) {
  7.             client = new AdvancedSSHClient(host, port, username);
  8.             client.connectWithPassword(password);
  9.         }
  10.         return client;
  11.     }
  12. }
复制代码

• 使用连接池:对于高并发场景,可以考虑使用连接池管理SSH连接:
  1. public class SSHConnectionPool {
  2.     private static final int MAX_POOL_SIZE = 10;
  3.     private static final Map<String, Queue<AdvancedSSHClient>> pool = new HashMap<>();
  4.    
  5.     public static synchronized AdvancedSSHClient borrowObject(String host, int port, String username, String password) throws JSchException {
  6.         String key = host + ":" + port + ":" + username;
  7.         Queue<AdvancedSSHClient> queue = pool.get(key);
  8.         
  9.         if (queue == null) {
  10.             queue = new LinkedList<>();
  11.             pool.put(key, queue);
  12.         }
  13.         
  14.         AdvancedSSHClient client = queue.poll();
  15.         if (client == null) {
  16.             client = new AdvancedSSHClient(host, port, username);
  17.             client.connectWithPassword(password);
  18.         }
  19.         
  20.         return client;
  21.     }
  22.    
  23.     public static synchronized void returnObject(AdvancedSSHClient client) {
  24.         String key = client.getHost() + ":" + client.getPort() + ":" + client.getUsername();
  25.         Queue<AdvancedSSHClient> queue = pool.get(key);
  26.         
  27.         if (queue != null && queue.size() < MAX_POOL_SIZE) {
  28.             queue.offer(client);
  29.         } else {
  30.             client.disconnect();
  31.         }
  32.     }
  33. }
复制代码

4. 日志记录

• 启用JSch日志:JSch提供了日志记录功能,可以帮助调试问题:
  1. // 启用JSch日志
  2. JSch.setLogger(new com.jcraft.jsch.Logger() {
  3.     @Override
  4.     public boolean isEnabled(int level) {
  5.         return true;
  6.     }
  7.    
  8.     @Override
  9.     public void log(int level, String message) {
  10.         System.out.println("[JSch] " + message);
  11.     }
  12. });
复制代码

• 记录操作日志:记录SSH操作的详细信息,便于审计和故障排除:
  1. public class LoggingSSHClient extends AdvancedSSHClient {
  2.     private java.util.logging.Logger logger = java.util.logging.Logger.getLogger(LoggingSSHClient.class.getName());
  3.    
  4.     public LoggingSSHClient(String host, int port, String username) {
  5.         super(host, port, username);
  6.     }
  7.    
  8.     @Override
  9.     public void connectWithPassword(String password) throws JSchException {
  10.         logger.info("尝试连接到 " + host + ":" + port + " 使用用户名 " + username);
  11.         try {
  12.             super.connectWithPassword(password);
  13.             logger.info("成功连接到 " + host + ":" + port);
  14.         } catch (JSchException e) {
  15.             logger.severe("连接失败: " + e.getMessage());
  16.             throw e;
  17.         }
  18.     }
  19.    
  20.     @Override
  21.     public String executeCommand(String command) throws JSchException, IOException {
  22.         logger.info("执行命令: " + command);
  23.         try {
  24.             String result = super.executeCommand(command);
  25.             logger.info("命令执行成功");
  26.             return result;
  27.         } catch (Exception e) {
  28.             logger.severe("命令执行失败: " + e.getMessage());
  29.             throw e;
  30.         }
  31.     }
  32. }
复制代码

替代方案

除了JSch,还有其他一些Java SSH库可以使用:

1. Apache MINA SSHD

Apache MINA SSHD是另一个流行的Java SSH库,它提供了更现代的API和更好的性能。
  1. import org.apache.sshd.client.SshClient;
  2. import org.apache.sshd.client.channel.ClientChannel;
  3. import org.apache.sshd.client.channel.ClientChannelEvent;
  4. import org.apache.sshd.client.session.ClientSession;
  5. import org.apache.sshd.common.channel.Channel;
  6. import java.io.ByteArrayOutputStream;
  7. import java.io.IOException;
  8. import java.io.OutputStream;
  9. import java.util.EnumSet;
  10. import java.util.concurrent.TimeUnit;
  11. public class ApacheSSHDExample {
  12.     public static void main(String[] args) throws IOException {
  13.         SshClient client = SshClient.setUpDefaultClient();
  14.         client.start();
  15.         
  16.         try (ClientSession session = client.connect("username", "hostname", 22)
  17.                 .verify(7, TimeUnit.SECONDS)
  18.                 .getSession()) {
  19.             session.addPasswordIdentity("password");
  20.             session.auth().verify(5, TimeUnit.SECONDS);
  21.             
  22.             try (ClientChannel channel = session.createChannel(Channel.CHANNEL_SHELL)) {
  23.                 channel.setOut(System.out);
  24.                 channel.setErr(System.err);
  25.                 channel.open().verify(5, TimeUnit.SECONDS);
  26.                
  27.                 try (OutputStream pipedIn = channel.getInvertedIn()) {
  28.                     pipedIn.write("ls -l\n".getBytes());
  29.                     pipedIn.flush();
  30.                 }
  31.                
  32.                 channel.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), TimeUnit.SECONDS.toMillis(5));
  33.             }
  34.         } finally {
  35.             client.stop();
  36.         }
  37.     }
  38. }
复制代码

2. Ganymed SSH-2

Ganymed SSH-2是一个纯Java实现的SSH-2协议库,虽然已经不再积极维护,但在一些遗留项目中仍然使用。
  1. import ch.ethz.ssh2.Connection;
  2. import ch.ethz.ssh2.Session;
  3. import ch.ethz.ssh2.StreamGobbler;
  4. import java.io.BufferedReader;
  5. import java.io.IOException;
  6. import java.io.InputStream;
  7. import java.io.InputStreamReader;
  8. public class GanymedSSHExample {
  9.     public static void main(String[] args) {
  10.         String hostname = "hostname";
  11.         String username = "username";
  12.         String password = "password";
  13.         
  14.         Connection conn = new Connection(hostname);
  15.         try {
  16.             conn.connect();
  17.             boolean isAuthenticated = conn.authenticateWithPassword(username, password);
  18.             
  19.             if (isAuthenticated) {
  20.                 Session sess = conn.openSession();
  21.                 sess.execCommand("ls -l");
  22.                
  23.                 InputStream stdout = new StreamGobbler(sess.getStdout());
  24.                 BufferedReader br = new BufferedReader(new InputStreamReader(stdout));
  25.                
  26.                 while (true) {
  27.                     String line = br.readLine();
  28.                     if (line == null)
  29.                         break;
  30.                     System.out.println(line);
  31.                 }
  32.                
  33.                 sess.close();
  34.             } else {
  35.                 System.err.println("认证失败");
  36.             }
  37.         } catch (IOException e) {
  38.             e.printStackTrace(System.err);
  39.         } finally {
  40.             conn.close();
  41.         }
  42.     }
  43. }
复制代码

总结

本文详细介绍了如何使用Java实现SSH客户端功能,无需依赖Xshell等第三方工具,直接通过代码建立安全连接、执行远程命令和管理服务器。我们使用了JSch库作为主要的实现工具,并提供了完整的代码示例和最佳实践建议。

通过本文,你学会了:

1. 如何建立SSH连接,包括密码认证和公钥认证
2. 如何执行远程命令,包括简单命令、交互式命令和长时间运行的命令
3. 如何进行文件传输,包括上传和下载文件和目录
4. 如何实现高级功能,如端口转发、X11转发和代理跳转
5. 如何遵循最佳实践,确保安全性和性能

这些知识可以帮助你在Java应用程序中集成SSH功能,实现自动化运维、远程监控、批量任务执行等场景。希望本文对你有所帮助!
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

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

本版积分规则