|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
引言
SSH(Secure Shell)是一种加密的网络协议,用于在不安全的网络中安全地进行远程操作。通常,我们使用Xshell、PuTTY等图形化工具来连接和管理远程服务器,但在某些场景下,我们可能需要在Java应用程序中直接实现SSH功能,例如自动化运维、远程监控、批量任务执行等。
本文将详细介绍如何使用Java实现SSH客户端功能,无需依赖Xshell等第三方工具,直接通过代码建立安全连接、执行远程命令和管理服务器。我们将使用Java中最流行的SSH库——JSch(Java Secure Channel)来实现这些功能。
准备工作
1. 添加JSch依赖
首先,我们需要在项目中添加JSch库的依赖。如果你使用Maven,可以在pom.xml中添加以下依赖:
- <dependency>
- <groupId>com.jcraft</groupId>
- <artifactId>jsch</artifactId>
- <version>0.1.55</version>
- </dependency>
复制代码
如果你使用Gradle,可以在build.gradle中添加:
- implementation 'com.jcraft:jsch:0.1.55'
复制代码
2. 了解SSH连接的基本要素
在开始编码之前,我们需要了解SSH连接的基本要素:
• 主机(Host):远程服务器的IP地址或域名
• 端口(Port):SSH服务监听的端口,默认为22
• 用户名(Username):登录远程服务器的用户名
• 认证方式:密码认证或公钥认证
• 超时时间:连接和操作的超时设置
建立基本SSH连接
1. 创建JSch实例并配置
- import com.jcraft.jsch.*;
- public class SSHClient {
- private JSch jsch;
- private Session session;
- private String host;
- private int port;
- private String username;
-
- public SSHClient(String host, int port, String username) {
- this.jsch = new JSch();
- this.host = host;
- this.port = port;
- this.username = username;
- }
-
- // 其他方法将在后面实现
- }
复制代码
2. 使用密码认证建立连接
- public void connectWithPassword(String password) throws JSchException {
- // 创建会话
- session = jsch.getSession(username, host, port);
-
- // 设置密码
- session.setPassword(password);
-
- // 严格主机密钥检查
- // 设置为"no"表示不检查,生产环境应该设置为"yes"并提供已知主机文件
- session.setConfig("StrictHostKeyChecking", "no");
-
- // 设置超时时间
- session.setTimeout(10000); // 10秒
-
- // 建立连接
- session.connect();
-
- System.out.println("SSH连接成功建立");
- }
复制代码
3. 使用公钥认证建立连接
- public void connectWithPublicKey(String privateKeyPath, String passphrase) throws JSchException {
- // 添加私钥
- if (passphrase == null || passphrase.isEmpty()) {
- jsch.addIdentity(privateKeyPath);
- } else {
- jsch.addIdentity(privateKeyPath, passphrase);
- }
-
- // 创建会话
- session = jsch.getSession(username, host, port);
-
- // 严格主机密钥检查
- session.setConfig("StrictHostKeyChecking", "no");
-
- // 设置超时时间
- session.setTimeout(10000); // 10秒
-
- // 建立连接
- session.connect();
-
- System.out.println("SSH连接成功建立");
- }
复制代码
4. 关闭连接
- public void disconnect() {
- if (session != null && session.isConnected()) {
- session.disconnect();
- System.out.println("SSH连接已关闭");
- }
- }
复制代码
执行远程命令
1. 执行简单命令
- public String executeCommand(String command) throws JSchException, IOException {
- if (session == null || !session.isConnected()) {
- throw new IllegalStateException("SSH会话未建立");
- }
-
- // 创建执行通道
- ChannelExec channel = (ChannelExec) session.openChannel("exec");
- channel.setCommand(command);
-
- // 获取输入流
- InputStream in = channel.getInputStream();
- InputStream err = channel.getExtInputStream();
-
- // 连接通道
- channel.connect();
-
- // 读取输出
- StringBuilder output = new StringBuilder();
- StringBuilder error = new StringBuilder();
-
- byte[] tmp = new byte[1024];
- while (true) {
- while (in.available() > 0) {
- int i = in.read(tmp, 0, 1024);
- if (i < 0) break;
- output.append(new String(tmp, 0, i));
- }
- while (err.available() > 0) {
- int i = err.read(tmp, 0, 1024);
- if (i < 0) break;
- error.append(new String(tmp, 0, i));
- }
- if (channel.isClosed()) {
- if (in.available() > 0 || err.available() > 0) continue;
- System.out.println("退出状态: " + channel.getExitStatus());
- break;
- }
- try {
- Thread.sleep(1000);
- } catch (Exception ee) {
- ee.printStackTrace();
- }
- }
-
- // 关闭通道
- channel.disconnect();
-
- // 如果有错误输出,抛出异常
- if (error.length() > 0) {
- throw new IOException("命令执行错误: " + error.toString());
- }
-
- return output.toString();
- }
复制代码
2. 执行交互式命令
有些命令需要用户输入,例如确认操作或输入密码。我们可以使用ChannelShell来实现:
- public String executeInteractiveCommand(String command, String input) throws JSchException, IOException {
- if (session == null || !session.isConnected()) {
- throw new IllegalStateException("SSH会话未建立");
- }
-
- // 创建Shell通道
- ChannelShell channel = (ChannelShell) session.openChannel("shell");
-
- // 获取输入输出流
- OutputStream out = channel.getOutputStream();
- InputStream in = channel.getInputStream();
-
- // 连接通道
- channel.connect();
-
- // 发送命令
- out.write((command + "\n").getBytes());
- out.flush();
-
- // 如果需要输入,发送输入
- if (input != null && !input.isEmpty()) {
- out.write((input + "\n").getBytes());
- out.flush();
- }
-
- // 读取输出
- StringBuilder output = new StringBuilder();
- byte[] tmp = new byte[1024];
- while (true) {
- while (in.available() > 0) {
- int i = in.read(tmp, 0, 1024);
- if (i < 0) break;
- output.append(new String(tmp, 0, i));
- }
- if (channel.isClosed()) {
- if (in.available() > 0) continue;
- System.out.println("退出状态: " + channel.getExitStatus());
- break;
- }
- try {
- Thread.sleep(1000);
- } catch (Exception ee) {
- ee.printStackTrace();
- }
- }
-
- // 关闭通道
- channel.disconnect();
-
- return output.toString();
- }
复制代码
3. 执行长时间运行的命令
对于长时间运行的命令,我们可能需要实时获取输出并能够随时终止命令:
- public void executeLongRunningCommand(String command, CommandOutputListener listener) throws JSchException, IOException {
- if (session == null || !session.isConnected()) {
- throw new IllegalStateException("SSH会话未建立");
- }
-
- // 创建执行通道
- ChannelExec channel = (ChannelExec) session.openChannel("exec");
- channel.setCommand(command);
-
- // 获取输入流
- InputStream in = channel.getInputStream();
- InputStream err = channel.getExtInputStream();
-
- // 连接通道
- channel.connect();
-
- // 创建线程读取输出
- Thread outputThread = new Thread(() -> {
- try {
- byte[] tmp = new byte[1024];
- while (true) {
- while (in.available() > 0) {
- int i = in.read(tmp, 0, 1024);
- if (i < 0) break;
- if (listener != null) {
- listener.onOutput(new String(tmp, 0, i));
- }
- }
- if (channel.isClosed()) {
- if (in.available() > 0) continue;
- if (listener != null) {
- listener.onExit(channel.getExitStatus());
- }
- break;
- }
- try {
- Thread.sleep(100);
- } catch (Exception ee) {
- ee.printStackTrace();
- }
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- });
-
- // 创建线程读取错误
- Thread errorThread = new Thread(() -> {
- try {
- byte[] tmp = new byte[1024];
- while (true) {
- while (err.available() > 0) {
- int i = err.read(tmp, 0, 1024);
- if (i < 0) break;
- if (listener != null) {
- listener.onError(new String(tmp, 0, i));
- }
- }
- if (channel.isClosed()) {
- break;
- }
- try {
- Thread.sleep(100);
- } catch (Exception ee) {
- ee.printStackTrace();
- }
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- });
-
- // 启动线程
- outputThread.start();
- errorThread.start();
-
- // 等待线程结束
- try {
- outputThread.join();
- errorThread.join();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
-
- // 关闭通道
- channel.disconnect();
- }
- // 命令输出监听器接口
- public interface CommandOutputListener {
- void onOutput(String output);
- void onError(String error);
- void onExit(int exitStatus);
- }
复制代码
文件传输
1. 上传文件
- public void uploadFile(String localFile, String remoteFile) throws JSchException, SftpException {
- if (session == null || !session.isConnected()) {
- throw new IllegalStateException("SSH会话未建立");
- }
-
- // 创建SFTP通道
- ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
- channel.connect();
-
- try {
- // 上传文件
- channel.put(localFile, remoteFile);
- System.out.println("文件上传成功: " + localFile + " -> " + remoteFile);
- } finally {
- // 关闭通道
- channel.disconnect();
- }
- }
复制代码
2. 下载文件
- public void downloadFile(String remoteFile, String localFile) throws JSchException, SftpException {
- if (session == null || !session.isConnected()) {
- throw new IllegalStateException("SSH会话未建立");
- }
-
- // 创建SFTP通道
- ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
- channel.connect();
-
- try {
- // 下载文件
- channel.get(remoteFile, localFile);
- System.out.println("文件下载成功: " + remoteFile + " -> " + localFile);
- } finally {
- // 关闭通道
- channel.disconnect();
- }
- }
复制代码
3. 上传目录
- public void uploadDirectory(String localDir, String remoteDir) throws JSchException, SftpException {
- if (session == null || !session.isConnected()) {
- throw new IllegalStateException("SSH会话未建立");
- }
-
- // 创建SFTP通道
- ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
- channel.connect();
-
- try {
- // 创建远程目录
- try {
- channel.mkdir(remoteDir);
- } catch (SftpException e) {
- // 目录可能已存在,忽略错误
- if (e.id != ChannelSftp.SSH_FX_FAILURE) {
- throw e;
- }
- }
-
- // 获取本地文件列表
- File localDirFile = new File(localDir);
- File[] files = localDirFile.listFiles();
- if (files != null) {
- for (File file : files) {
- if (file.isDirectory()) {
- // 递归上传子目录
- uploadDirectory(file.getAbsolutePath(), remoteDir + "/" + file.getName());
- } else {
- // 上传文件
- channel.put(file.getAbsolutePath(), remoteDir + "/" + file.getName());
- System.out.println("文件上传成功: " + file.getAbsolutePath() + " -> " + remoteDir + "/" + file.getName());
- }
- }
- }
- } finally {
- // 关闭通道
- channel.disconnect();
- }
- }
复制代码
4. 下载目录
- public void downloadDirectory(String remoteDir, String localDir) throws JSchException, SftpException {
- if (session == null || !session.isConnected()) {
- throw new IllegalStateException("SSH会话未建立");
- }
-
- // 创建SFTP通道
- ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
- channel.connect();
-
- try {
- // 创建本地目录
- File localDirFile = new File(localDir);
- if (!localDirFile.exists()) {
- localDirFile.mkdirs();
- }
-
- // 获取远程文件列表
- Vector<ChannelSftp.LsEntry> files = channel.ls(remoteDir);
- for (ChannelSftp.LsEntry file : files) {
- String fileName = file.getFilename();
- if (".".equals(fileName) || "..".equals(fileName)) {
- continue;
- }
-
- String remotePath = remoteDir + "/" + fileName;
- String localPath = localDir + "/" + fileName;
-
- if (file.getAttrs().isDir()) {
- // 递归下载子目录
- downloadDirectory(remotePath, localPath);
- } else {
- // 下载文件
- channel.get(remotePath, localPath);
- System.out.println("文件下载成功: " + remotePath + " -> " + localPath);
- }
- }
- } finally {
- // 关闭通道
- channel.disconnect();
- }
- }
复制代码
高级功能
1. 端口转发
SSH端口转发允许我们将本地端口映射到远程服务器的端口,或者将远程服务器的端口映射到本地端口。
- public void setLocalPortForward(int localPort, String remoteHost, int remotePort) throws JSchException {
- if (session == null || !session.isConnected()) {
- throw new IllegalStateException("SSH会话未建立");
- }
-
- // 设置本地端口转发
- session.setPortForwardingL(localPort, remoteHost, remotePort);
- System.out.println("本地端口转发设置成功: localhost:" + localPort + " -> " + remoteHost + ":" + remotePort);
- }
复制代码- public void setRemotePortForward(int remotePort, String localHost, int localPort) throws JSchException {
- if (session == null || !session.isConnected()) {
- throw new IllegalStateException("SSH会话未建立");
- }
-
- // 设置远程端口转发
- session.setPortForwardingR(remotePort, localHost, localPort);
- System.out.println("远程端口转发设置成功: " + host + ":" + remotePort + " -> " + localHost + ":" + localPort);
- }
复制代码
2. 动态端口转发(SOCKS代理)
- public void setDynamicPortForward(int localPort) throws JSchException {
- if (session == null || !session.isConnected()) {
- throw new IllegalStateException("SSH会话未建立");
- }
-
- // 设置动态端口转发(SOCKS代理)
- session.setPortForwardingL(localPort, "", 0);
- System.out.println("动态端口转发设置成功: localhost:" + localPort + " (SOCKS代理)");
- }
复制代码
3. X11转发
- public void enableX11Forwarding() throws JSchException {
- if (session == null || !session.isConnected()) {
- throw new IllegalStateException("SSH会话未建立");
- }
-
- // 启用X11转发
- session.setX11Host("localhost");
- session.setX11Port(6000);
- session.setX11Cookie(new File("/tmp/.X11-unix/X0"));
-
- System.out.println("X11转发已启用");
- }
复制代码
4. 代理跳转
- public void connectViaProxy(String proxyHost, int proxyPort, String proxyUsername,
- String proxyPassword, String targetHost, int targetPort,
- String targetUsername, String targetPassword) throws JSchException {
-
- // 创建到代理服务器的会话
- Session proxySession = jsch.getSession(proxyUsername, proxyHost, proxyPort);
- proxySession.setPassword(proxyPassword);
- proxySession.setConfig("StrictHostKeyChecking", "no");
- proxySession.connect();
-
- try {
- // 创建到目标服务器的会话
- Session targetSession = jsch.getSession(targetUsername, targetHost, targetPort);
- targetSession.setPassword(targetPassword);
- targetSession.setConfig("StrictHostKeyChecking", "no");
-
- // 设置代理跳转
- targetSession.setProxy(new ProxyHTTP(proxyHost, proxyPort));
-
- // 连接到目标服务器
- targetSession.connect();
-
- // 保存目标会话
- this.session = targetSession;
-
- System.out.println("通过代理跳转连接成功");
- } catch (JSchException e) {
- // 如果连接失败,关闭代理会话
- proxySession.disconnect();
- throw e;
- }
- }
复制代码
完整示例
下面是一个完整的SSH客户端实现,集成了我们前面讨论的所有功能:
- import com.jcraft.jsch.*;
- import java.io.*;
- import java.util.Vector;
- public class AdvancedSSHClient {
- private JSch jsch;
- private Session session;
- private String host;
- private int port;
- private String username;
-
- public AdvancedSSHClient(String host, int port, String username) {
- this.jsch = new JSch();
- this.host = host;
- this.port = port;
- this.username = username;
- }
-
- // 连接方法
- public void connectWithPassword(String password) throws JSchException {
- session = jsch.getSession(username, host, port);
- session.setPassword(password);
- session.setConfig("StrictHostKeyChecking", "no");
- session.setTimeout(10000);
- session.connect();
- System.out.println("SSH连接成功建立");
- }
-
- public void connectWithPublicKey(String privateKeyPath, String passphrase) throws JSchException {
- if (passphrase == null || passphrase.isEmpty()) {
- jsch.addIdentity(privateKeyPath);
- } else {
- jsch.addIdentity(privateKeyPath, passphrase);
- }
-
- session = jsch.getSession(username, host, port);
- session.setConfig("StrictHostKeyChecking", "no");
- session.setTimeout(10000);
- session.connect();
- System.out.println("SSH连接成功建立");
- }
-
- // 断开连接
- public void disconnect() {
- if (session != null && session.isConnected()) {
- session.disconnect();
- System.out.println("SSH连接已关闭");
- }
- }
-
- // 执行命令
- public String executeCommand(String command) throws JSchException, IOException {
- if (session == null || !session.isConnected()) {
- throw new IllegalStateException("SSH会话未建立");
- }
-
- ChannelExec channel = (ChannelExec) session.openChannel("exec");
- channel.setCommand(command);
-
- InputStream in = channel.getInputStream();
- InputStream err = channel.getExtInputStream();
- channel.connect();
-
- StringBuilder output = new StringBuilder();
- StringBuilder error = new StringBuilder();
-
- byte[] tmp = new byte[1024];
- while (true) {
- while (in.available() > 0) {
- int i = in.read(tmp, 0, 1024);
- if (i < 0) break;
- output.append(new String(tmp, 0, i));
- }
- while (err.available() > 0) {
- int i = err.read(tmp, 0, 1024);
- if (i < 0) break;
- error.append(new String(tmp, 0, i));
- }
- if (channel.isClosed()) {
- if (in.available() > 0 || err.available() > 0) continue;
- System.out.println("退出状态: " + channel.getExitStatus());
- break;
- }
- try {
- Thread.sleep(1000);
- } catch (Exception ee) {
- ee.printStackTrace();
- }
- }
-
- channel.disconnect();
-
- if (error.length() > 0) {
- throw new IOException("命令执行错误: " + error.toString());
- }
-
- return output.toString();
- }
-
- // 文件传输
- public void uploadFile(String localFile, String remoteFile) throws JSchException, SftpException {
- if (session == null || !session.isConnected()) {
- throw new IllegalStateException("SSH会话未建立");
- }
-
- ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
- channel.connect();
-
- try {
- channel.put(localFile, remoteFile);
- System.out.println("文件上传成功: " + localFile + " -> " + remoteFile);
- } finally {
- channel.disconnect();
- }
- }
-
- public void downloadFile(String remoteFile, String localFile) throws JSchException, SftpException {
- if (session == null || !session.isConnected()) {
- throw new IllegalStateException("SSH会话未建立");
- }
-
- ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
- channel.connect();
-
- try {
- channel.get(remoteFile, localFile);
- System.out.println("文件下载成功: " + remoteFile + " -> " + localFile);
- } finally {
- channel.disconnect();
- }
- }
-
- // 端口转发
- public void setLocalPortForward(int localPort, String remoteHost, int remotePort) throws JSchException {
- if (session == null || !session.isConnected()) {
- throw new IllegalStateException("SSH会话未建立");
- }
-
- session.setPortForwardingL(localPort, remoteHost, remotePort);
- System.out.println("本地端口转发设置成功: localhost:" + localPort + " -> " + remoteHost + ":" + remotePort);
- }
-
- public void setRemotePortForward(int remotePort, String localHost, int localPort) throws JSchException {
- if (session == null || !session.isConnected()) {
- throw new IllegalStateException("SSH会话未建立");
- }
-
- session.setPortForwardingR(remotePort, localHost, localPort);
- System.out.println("远程端口转发设置成功: " + host + ":" + remotePort + " -> " + localHost + ":" + localPort);
- }
-
- public void setDynamicPortForward(int localPort) throws JSchException {
- if (session == null || !session.isConnected()) {
- throw new IllegalStateException("SSH会话未建立");
- }
-
- session.setPortForwardingL(localPort, "", 0);
- System.out.println("动态端口转发设置成功: localhost:" + localPort + " (SOCKS代理)");
- }
-
- // 使用示例
- public static void main(String[] args) {
- try {
- // 创建SSH客户端
- AdvancedSSHClient client = new AdvancedSSHClient("example.com", 22, "username");
-
- // 使用密码连接
- client.connectWithPassword("password");
-
- // 执行命令
- String result = client.executeCommand("ls -l");
- System.out.println("命令执行结果:");
- System.out.println(result);
-
- // 上传文件
- client.uploadFile("/path/to/local/file.txt", "/path/to/remote/file.txt");
-
- // 下载文件
- client.downloadFile("/path/to/remote/file.txt", "/path/to/local/downloaded_file.txt");
-
- // 设置本地端口转发
- client.setLocalPortForward(8080, "localhost", 80);
-
- // 断开连接
- client.disconnect();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
复制代码
最佳实践和注意事项
1. 安全性考虑
• 主机密钥验证:在生产环境中,应该启用严格的主机密钥检查,而不是简单地设置为”no”:
- // 使用已知主机文件
- jsch.setKnownHosts("/path/to/known_hosts");
复制代码
• 密码安全:避免在代码中硬编码密码,可以使用配置文件或环境变量:
- // 从环境变量获取密码
- String password = System.getenv("SSH_PASSWORD");
复制代码
• 使用公钥认证:公钥认证比密码认证更安全,建议在生产环境中使用:
- // 使用公钥认证
- client.connectWithPublicKey("/path/to/private/key", "passphrase");
复制代码
2. 错误处理
• 异常处理:正确处理JSchException和IOException,提供有意义的错误信息:
- try {
- client.connectWithPassword(password);
- String result = client.executeCommand(command);
- System.out.println(result);
- } catch (JSchException e) {
- System.err.println("SSH连接错误: " + e.getMessage());
- } catch (IOException e) {
- System.err.println("命令执行错误: " + e.getMessage());
- } finally {
- client.disconnect();
- }
复制代码
• 超时设置:为连接和操作设置合理的超时时间,避免长时间等待:
- // 设置连接超时
- session.setTimeout(10000); // 10秒
- // 设置通道超时
- channel.connect(5000); // 5秒
复制代码
3. 性能优化
• 重用会话:避免频繁创建和关闭会话,尽量重用已建立的会话:
- // 创建单例SSH客户端
- public class SSHClientManager {
- private static AdvancedSSHClient client;
-
- public static synchronized AdvancedSSHClient getClient(String host, int port, String username, String password) throws JSchException {
- if (client == null || !client.isConnected()) {
- client = new AdvancedSSHClient(host, port, username);
- client.connectWithPassword(password);
- }
- return client;
- }
- }
复制代码
• 使用连接池:对于高并发场景,可以考虑使用连接池管理SSH连接:
- public class SSHConnectionPool {
- private static final int MAX_POOL_SIZE = 10;
- private static final Map<String, Queue<AdvancedSSHClient>> pool = new HashMap<>();
-
- public static synchronized AdvancedSSHClient borrowObject(String host, int port, String username, String password) throws JSchException {
- String key = host + ":" + port + ":" + username;
- Queue<AdvancedSSHClient> queue = pool.get(key);
-
- if (queue == null) {
- queue = new LinkedList<>();
- pool.put(key, queue);
- }
-
- AdvancedSSHClient client = queue.poll();
- if (client == null) {
- client = new AdvancedSSHClient(host, port, username);
- client.connectWithPassword(password);
- }
-
- return client;
- }
-
- public static synchronized void returnObject(AdvancedSSHClient client) {
- String key = client.getHost() + ":" + client.getPort() + ":" + client.getUsername();
- Queue<AdvancedSSHClient> queue = pool.get(key);
-
- if (queue != null && queue.size() < MAX_POOL_SIZE) {
- queue.offer(client);
- } else {
- client.disconnect();
- }
- }
- }
复制代码
4. 日志记录
• 启用JSch日志:JSch提供了日志记录功能,可以帮助调试问题:
- // 启用JSch日志
- JSch.setLogger(new com.jcraft.jsch.Logger() {
- @Override
- public boolean isEnabled(int level) {
- return true;
- }
-
- @Override
- public void log(int level, String message) {
- System.out.println("[JSch] " + message);
- }
- });
复制代码
• 记录操作日志:记录SSH操作的详细信息,便于审计和故障排除:
- public class LoggingSSHClient extends AdvancedSSHClient {
- private java.util.logging.Logger logger = java.util.logging.Logger.getLogger(LoggingSSHClient.class.getName());
-
- public LoggingSSHClient(String host, int port, String username) {
- super(host, port, username);
- }
-
- @Override
- public void connectWithPassword(String password) throws JSchException {
- logger.info("尝试连接到 " + host + ":" + port + " 使用用户名 " + username);
- try {
- super.connectWithPassword(password);
- logger.info("成功连接到 " + host + ":" + port);
- } catch (JSchException e) {
- logger.severe("连接失败: " + e.getMessage());
- throw e;
- }
- }
-
- @Override
- public String executeCommand(String command) throws JSchException, IOException {
- logger.info("执行命令: " + command);
- try {
- String result = super.executeCommand(command);
- logger.info("命令执行成功");
- return result;
- } catch (Exception e) {
- logger.severe("命令执行失败: " + e.getMessage());
- throw e;
- }
- }
- }
复制代码
替代方案
除了JSch,还有其他一些Java SSH库可以使用:
1. Apache MINA SSHD
Apache MINA SSHD是另一个流行的Java SSH库,它提供了更现代的API和更好的性能。
- import org.apache.sshd.client.SshClient;
- import org.apache.sshd.client.channel.ClientChannel;
- import org.apache.sshd.client.channel.ClientChannelEvent;
- import org.apache.sshd.client.session.ClientSession;
- import org.apache.sshd.common.channel.Channel;
- import java.io.ByteArrayOutputStream;
- import java.io.IOException;
- import java.io.OutputStream;
- import java.util.EnumSet;
- import java.util.concurrent.TimeUnit;
- public class ApacheSSHDExample {
- public static void main(String[] args) throws IOException {
- SshClient client = SshClient.setUpDefaultClient();
- client.start();
-
- try (ClientSession session = client.connect("username", "hostname", 22)
- .verify(7, TimeUnit.SECONDS)
- .getSession()) {
- session.addPasswordIdentity("password");
- session.auth().verify(5, TimeUnit.SECONDS);
-
- try (ClientChannel channel = session.createChannel(Channel.CHANNEL_SHELL)) {
- channel.setOut(System.out);
- channel.setErr(System.err);
- channel.open().verify(5, TimeUnit.SECONDS);
-
- try (OutputStream pipedIn = channel.getInvertedIn()) {
- pipedIn.write("ls -l\n".getBytes());
- pipedIn.flush();
- }
-
- channel.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), TimeUnit.SECONDS.toMillis(5));
- }
- } finally {
- client.stop();
- }
- }
- }
复制代码
2. Ganymed SSH-2
Ganymed SSH-2是一个纯Java实现的SSH-2协议库,虽然已经不再积极维护,但在一些遗留项目中仍然使用。
- import ch.ethz.ssh2.Connection;
- import ch.ethz.ssh2.Session;
- import ch.ethz.ssh2.StreamGobbler;
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- public class GanymedSSHExample {
- public static void main(String[] args) {
- String hostname = "hostname";
- String username = "username";
- String password = "password";
-
- Connection conn = new Connection(hostname);
- try {
- conn.connect();
- boolean isAuthenticated = conn.authenticateWithPassword(username, password);
-
- if (isAuthenticated) {
- Session sess = conn.openSession();
- sess.execCommand("ls -l");
-
- InputStream stdout = new StreamGobbler(sess.getStdout());
- BufferedReader br = new BufferedReader(new InputStreamReader(stdout));
-
- while (true) {
- String line = br.readLine();
- if (line == null)
- break;
- System.out.println(line);
- }
-
- sess.close();
- } else {
- System.err.println("认证失败");
- }
- } catch (IOException e) {
- e.printStackTrace(System.err);
- } finally {
- conn.close();
- }
- }
- }
复制代码
总结
本文详细介绍了如何使用Java实现SSH客户端功能,无需依赖Xshell等第三方工具,直接通过代码建立安全连接、执行远程命令和管理服务器。我们使用了JSch库作为主要的实现工具,并提供了完整的代码示例和最佳实践建议。
通过本文,你学会了:
1. 如何建立SSH连接,包括密码认证和公钥认证
2. 如何执行远程命令,包括简单命令、交互式命令和长时间运行的命令
3. 如何进行文件传输,包括上传和下载文件和目录
4. 如何实现高级功能,如端口转发、X11转发和代理跳转
5. 如何遵循最佳实践,确保安全性和性能
这些知识可以帮助你在Java应用程序中集成SSH功能,实现自动化运维、远程监控、批量任务执行等场景。希望本文对你有所帮助! |
|