目录
- SSH 与 Java 简介
- 准备工作:添加 JSch 依赖
- 核心概念:JSch 的主要组件
- 执行远程命令
- 基础示例
- 代码解析
- 处理交互式命令(需要输入密码/yes/no)
- 文件传输 (SFTP)
- 上传文件
- 下载文件
- 代码解析
- 端口转发 (隧道)
- 本地端口转发
- 代码解析
- 最佳实践与注意事项
- 使用密钥认证代替密码
- 使用
SessionConfig优化连接 - 异常处理
- 资源关闭
SSH 与 Java 简介
SSH (Secure Shell) 是一种网络协议,用于计算机之间的安全通信和数据传输,它广泛用于系统管理员远程管理服务器、开发者部署代码等场景。

在 Java 世界中,我们无法直接使用操作系统自带的 ssh 命令,我们需要借助第三方库来实现 SSH 功能。JSch 是 Java 平台下最流行、功能最全面的 SSH 客户端库,它实现了 SSH2 协议,支持以下核心功能:
- 远程命令执行:就像在终端里输入命令一样。
- 文件传输:通过 SFTP (SSH File Transfer Protocol) 安全地传输文件。
- 端口转发:将本地或远程端口的数据通过 SSH 隧道转发,实现安全访问。
准备工作:添加 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' // 建议使用最新版本
核心概念:JSch 的主要组件
在编写代码前,了解 JSch 的几个核心类非常重要:

JSch: 这是主入口点,用于创建和管理 SSH 会话,所有操作都通过它来初始化。Session: 代表一个到远程服务器的 SSH 会话,它包含了连接信息(主机、端口、用户名等)和配置,在执行任何操作前,必须先建立一个Session并连接。Channel: 代表一个通信通道。Session本身不直接传输数据,而是通过打开不同的Channel来实现具体功能。ChannelExec: 用于执行远程命令。ChannelSftp: 用于文件传输。ChannelShell/ChannelExec: 用于交互式会话。
UserInfo: 一个接口,用于在连接过程中处理用户交互,比如输入密码、确认主机密钥等。
实战一:执行远程命令
这是最基本的功能,比如在远程服务器上执行 ls -l 或 ps aux。
基础示例
import com.jcraft.jsch.*;
public class SshCommandExample {
public static void main(String[] args) {
String host = "your.server.com";
String user = "your_username";
String password = "your_password";
int port = 22;
JSch jsch = new JSch();
Session session = null;
ChannelExec channel = null;
try {
// 1. 创建 Session
session = jsch.getSession(user, host, port);
session.setPassword(password);
// 2. 设置严格的主机密钥检查
// 严格模式下,如果远程主机的密钥不在 known_hosts 文件中,会抛出异常
// 为方便测试,这里设置为 NO,生产环境建议使用 YES 或 ASK
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
// 3. 连接远程服务器
System.out.println("Connecting to " + host + "...");
session.connect();
// 4. 创建 Channel 执行命令
String command = "ls -l /tmp"; // 要执行的命令
channel = (ChannelExec) session.openChannel("exec");
channel.setCommand(command);
// 5. 获取命令的输入流并读取输出
InputStream in = channel.getInputStream();
InputStream err = channel.extInputStream(); // 获取错误流
System.out.println("Executing command: " + command);
channel.connect();
// 读取命令的标准输出
String output = readStream(in);
System.out.println("--- Command Output ---");
System.out.println(output);
// 读取命令的错误输出
String error = readStream(err);
if (!error.isEmpty()) {
System.err.println("--- Command Error ---");
System.err.println(error);
}
} catch (JSchException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 6. 关闭资源
if (channel != null) {
channel.disconnect();
}
if (session != null) {
session.disconnect();
}
}
}
private static String readStream(InputStream in) throws IOException {
StringBuilder sb = new StringBuilder();
byte[] buffer = new byte[1024];
while (true) {
int bytesRead = in.read(buffer);
if (bytesRead == -1) {
break;
}
sb.append(new String(buffer, 0, bytesRead));
}
return sb.toString();
}
}
代码解析
JSch jsch = new JSch();: 创建 JSch 实例。jsch.getSession(user, host, port);: 使用用户名、主机和端口创建一个Session对象。session.setPassword(password);: 设置密码。session.setConfig(config);: 设置会话配置。StrictHostKeyChecking是一个重要配置。yes: 默认值,拒绝连接不在known_hosts中的主机。no: 自动接受所有主机密钥,不安全,仅用于测试。ask: (默认行为)如果主机密钥未知,会通过UserInfo询问用户。
session.connect();: 建立连接,这是阻塞调用,直到连接成功或失败。session.openChannel("exec");: 打开一个类型为 "exec" 的通道,用于执行命令。channel.setCommand("ls -l /tmp");: 设置要执行的命令。channel.connect();: 连接通道,命令开始执行。channel.getInputStream(): 获取命令的标准输出流。finally块: 至关重要! 必须确保关闭Channel和Session,否则会导致资源泄漏。
实战二:文件传输
使用 SFTP 协议上传或下载文件。
上传文件
import com.jcraft.jsch.*;
public class SftpUploadExample {
public static void main(String[] args) {
String host = "your.server.com";
String user = "your_username";
String password = "your_password";
int port = 22;
String localFile = "C:/path/to/local/file.txt";
String remoteDir = "/tmp/"; // 远程目录,必须以 '/'
JSch jsch = new JSch();
Session session = null;
ChannelSftp channelSftp = null;
try {
session = jsch.getSession(user, host, port);
session.setPassword(password);
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.connect();
// 打开 SFTP 通道
Channel channel = session.openChannel("sftp");
channel.connect();
channelSftp = (ChannelSftp) channel;
System.out.println("Uploading " + localFile + " to " + remoteDir);
// 上传文件
channelSftp.put(localFile, remoteDir);
System.out.println("Upload complete.");
} catch (JSchException | SftpException e) {
e.printStackTrace();
} finally {
if (channelSftp != null) {
channelSftp.disconnect();
}
if (session != null) {
session.disconnect();
}
}
}
}
下载文件
下载文件与上传类似,只需调用 get() 方法。
// ... (连接代码与上面相同)
// 下载文件
String remoteFile = "/tmp/file.txt";
String localDir = "C:/path/to/local/download/";
System.out.println("Downloading " + remoteFile + " to " + localDir);
// 下载文件
channelSftp.get(remoteFile, localDir);
System.out.println("Download complete.");
// ... (关闭资源代码与上面相同)
代码解析
session.openChannel("sftp");: 打开类型为 "sftp" 的通道。channelSftp = (ChannelSftp) channel;: 将通Channel转换为ChannelSftp。channelSftp.put(localFile, remoteDir);: 上传文件,第一个参数是本地文件路径,第二个参数是远程目标路径(可以是目录或完整文件名)。channelSftp.get(remoteFile, localDir);: 下载文件,第一个参数是远程文件路径,第二个参数是本地目标路径。
实战三:端口转发
端口转发(或称 SSH 隧道)非常强大,可以让你安全地访问远程服务。
本地端口转发
场景:你的本地电脑无法直接访问数据库,但远程服务器 server.com 可以,数据库在 server.com 的 3306 端口上运行。
目标:在本地 localhost:3307 端口上访问,实际流量会被安全地转发到 server.com 的 3306 端口。
import com.jcraft.jsch.*;
public class PortForwardingExample {
public static void main(String[] args) {
String host = "your.server.com";
String user = "your_username";
String password = "your_password";
int port = 22;
// 转发配置
int localPort = 3307; // 本地监听端口
String remoteHost = "localhost"; // 远程服务器上数据库的地址 (通常是 localhost)
int remotePort = 3306; // 远程服务器上数据库的端口
JSch jsch = new JSch();
Session session = null;
try {
session = jsch.getSession(user, host, port);
session.setPassword(password);
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.connect();
// 设置端口转发
// 参数: 本地端口, 远程主机, 远程端口, Session
session.setPortForwardingR(localPort, remoteHost, remotePort);
System.out.println("Port forwarding established.");
System.out.println("You can now connect to localhost:" + localPort);
System.out.println("Press Enter to stop the forwarding.");
// 保持程序运行,直到用户按下回车
System.in.read();
} catch (JSchException | IOException e) {
e.printStackTrace();
} finally {
if (session != null) {
// 关闭端口转发
session.delPortForwardingR(localPort);
session.disconnect();
System.out.println("Port forwarding closed.");
}
}
}
}
代码解析
session.setPortForwardingR(localPort, remoteHost, remotePort);:R代表 "Remote Port Forwarding" 的本地端(即本地转发)。localPort: 本地机器上要监听的端口。remoteHost: 流量最终要到达的远程主机(在远程服务器看来)。remotePort: 流量最终要到达的远程端口。
session.delPortForwardingR(localPort);: 在关闭会话前,先取消端口转发设置。
最佳实践与注意事项
使用密钥认证代替密码
密码认证容易受到暴力破解攻击,使用 SSH 密钥对(公钥/私钥)更安全。
-
生成密钥对(在本地机器上,通常已经存在):
ssh-keygen -t rsa -b 4096
这会生成
~/.ssh/id_rsa(私钥) 和~/.ssh/id_rsa.pub(公钥)。 -
将公钥上传到远程服务器:
ssh-copy-id -i ~/.ssh/id_rsa.pub your_username@your.server.com
这会将你的公钥追加到远程服务器
~/.ssh/authorized_keys文件中。 -
修改 Java 代码使用密钥:
JSch jsch = new JSch(); // 添加私钥,JSch 会自动去 ~/.ssh/ 目录下寻找 jsch.addIdentity("~/.ssh/id_rsa"); Session session = jsch.getSession("your_username", "your.server.com", 22); // 不再需要 setPassword // session.setPassword("..."); // 注意:使用密钥时,如果服务器要求密码,你仍然需要提供 UserInfo // UserInfo userInfo = ...; // session.setUserInfo(userInfo);
使用 SessionConfig 优化连接
可以通过 setConfig 调整连接行为,
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "ask"); // 推荐生产环境使用
config.put("PreferredAuthentications", "publickey,password"); // 优先使用密钥认证
config.put("Compression", "yes"); // 启用压缩
session.setConfig(config);
异常处理
JSchException 和 SftpException 等都是检查型异常,必须处理,异常信息通常能帮助定位问题,如认证失败、网络不通、命令不存在等。
资源关闭
务必在 finally 块中关闭所有打开的资源,顺序通常是:先关闭 Channel,再关闭 Session,如果忘记关闭,会导致服务器上的会话和通道资源无法释放,可能造成服务器资源耗尽。
本教程涵盖了使用 Java 和 JSch 库进行 SSH 操作的核心内容:
- 执行命令:通过
ChannelExec实现,适合自动化运维任务。 - 文件传输:通过
ChannelSftp实现,适合备份、部署等场景。 - 端口转发:通过
Session.setPortForwarding实现,是安全访问内网服务的利器。
掌握了这些,你就可以在 Java 应用中实现强大的远程服务器管理和数据交互功能,在生产环境中,请务必使用密钥认证并妥善管理你的私钥文件。
